diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/index.js b/src/index.js index aa515c8..3791291 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); +core.particles = require('./particles'); core.accessibility = require('./accessibility'); // export a premade loader instance diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/index.js b/src/index.js index aa515c8..3791291 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); +core.particles = require('./particles'); core.accessibility = require('./accessibility'); // export a premade loader instance diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js new file mode 100644 index 0000000..1af1cc2 --- /dev/null +++ b/src/particles/ParticleContainer.js @@ -0,0 +1,331 @@ +var core = require('../core'); + +/** + * The ParticleContainer class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced + * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new ParticleContainer(); + * + * for (var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @extends PIXI.Container + * @memberof PIXI + * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. + * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. + * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. + * @param [properties.position=true] {boolean} When true, position be uploaded and applied. + * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. + * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. + * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. + * @param [batchSize=15000] {number} Number of particles per batch. + */ +function ParticleContainer(maxSize, properties, batchSize) +{ + core.Container.call(this); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); +} + +ParticleContainer.prototype = Object.create(core.Container.prototype); +ParticleContainer.prototype.constructor = ParticleContainer; +module.exports = ParticleContainer; + +/** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ +ParticleContainer.prototype.setProperties = function(properties) +{ + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; + } +}; + +/** + * Updates the object transform for rendering + * + * @private + */ +ParticleContainer.prototype.updateTransform = function () +{ + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ +ParticleContainer.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + + if(!this.baseTexture) + { + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) + { + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this) + } + } + + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Set the flag that static data should be updated to true + * + * @private + */ +ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) +{ + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ +ParticleContainer.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + positionX, + positionY, + finalWidth, + finalHeight + ); + } +}; + +/** + * Destroys the container + * + * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well + */ +ParticleContainer.prototype.destroy = function () { + Container.prototype.destroy.apply(this, arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; +}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/index.js b/src/index.js index aa515c8..3791291 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); +core.particles = require('./particles'); core.accessibility = require('./accessibility'); // export a premade loader instance diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js new file mode 100644 index 0000000..1af1cc2 --- /dev/null +++ b/src/particles/ParticleContainer.js @@ -0,0 +1,331 @@ +var core = require('../core'); + +/** + * The ParticleContainer class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced + * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new ParticleContainer(); + * + * for (var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @extends PIXI.Container + * @memberof PIXI + * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. + * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. + * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. + * @param [properties.position=true] {boolean} When true, position be uploaded and applied. + * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. + * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. + * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. + * @param [batchSize=15000] {number} Number of particles per batch. + */ +function ParticleContainer(maxSize, properties, batchSize) +{ + core.Container.call(this); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); +} + +ParticleContainer.prototype = Object.create(core.Container.prototype); +ParticleContainer.prototype.constructor = ParticleContainer; +module.exports = ParticleContainer; + +/** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ +ParticleContainer.prototype.setProperties = function(properties) +{ + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; + } +}; + +/** + * Updates the object transform for rendering + * + * @private + */ +ParticleContainer.prototype.updateTransform = function () +{ + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ +ParticleContainer.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + + if(!this.baseTexture) + { + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) + { + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this) + } + } + + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Set the flag that static data should be updated to true + * + * @private + */ +ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) +{ + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ +ParticleContainer.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + positionX, + positionY, + finalWidth, + finalHeight + ); + } +}; + +/** + * Destroys the container + * + * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well + */ +ParticleContainer.prototype.destroy = function () { + Container.prototype.destroy.apply(this, arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; +}; diff --git a/src/particles/index.js b/src/particles/index.js new file mode 100644 index 0000000..4a93acb --- /dev/null +++ b/src/particles/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI extras library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/pixijs/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI.mesh + */ +module.exports = { + ParticleContainer: require('./ParticleContainer'), + ParticleRenderer: require('./webgl/ParticleRenderer') +}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/index.js b/src/index.js index aa515c8..3791291 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); +core.particles = require('./particles'); core.accessibility = require('./accessibility'); // export a premade loader instance diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js new file mode 100644 index 0000000..1af1cc2 --- /dev/null +++ b/src/particles/ParticleContainer.js @@ -0,0 +1,331 @@ +var core = require('../core'); + +/** + * The ParticleContainer class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced + * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new ParticleContainer(); + * + * for (var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @extends PIXI.Container + * @memberof PIXI + * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. + * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. + * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. + * @param [properties.position=true] {boolean} When true, position be uploaded and applied. + * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. + * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. + * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. + * @param [batchSize=15000] {number} Number of particles per batch. + */ +function ParticleContainer(maxSize, properties, batchSize) +{ + core.Container.call(this); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); +} + +ParticleContainer.prototype = Object.create(core.Container.prototype); +ParticleContainer.prototype.constructor = ParticleContainer; +module.exports = ParticleContainer; + +/** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ +ParticleContainer.prototype.setProperties = function(properties) +{ + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; + } +}; + +/** + * Updates the object transform for rendering + * + * @private + */ +ParticleContainer.prototype.updateTransform = function () +{ + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ +ParticleContainer.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + + if(!this.baseTexture) + { + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) + { + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this) + } + } + + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Set the flag that static data should be updated to true + * + * @private + */ +ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) +{ + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ +ParticleContainer.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + positionX, + positionY, + finalWidth, + finalHeight + ); + } +}; + +/** + * Destroys the container + * + * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well + */ +ParticleContainer.prototype.destroy = function () { + Container.prototype.destroy.apply(this, arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; +}; diff --git a/src/particles/index.js b/src/particles/index.js new file mode 100644 index 0000000..4a93acb --- /dev/null +++ b/src/particles/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI extras library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/pixijs/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI.mesh + */ +module.exports = { + ParticleContainer: require('./ParticleContainer'), + ParticleRenderer: require('./webgl/ParticleRenderer') +}; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..ba79181 --- /dev/null +++ b/src/particles/webgl/ParticleBuffer.js @@ -0,0 +1,226 @@ +var glCore = require('pixi-gl-core'), + createIndicesForQuads = require('../../core/utils/createIndicesForQuads'); + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers 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 + */ + +/** + * The particle buffer manages the static and dynamic buffers for a particle container. + * + * @class + * @private + * @memberof PIXI + */ +function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) +{ + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; + + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) + { + var property = properties[i]; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } + } + + 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 + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size) + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); + } +}; + +/** + * Uploads the dynamic properties. + * + */ +ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) +{ + var gl = this.gl; + + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } + + this.dynamicBuffer.upload(); +}; + +/** + * Uploads the static properties. + * + */ +ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) +{ + var gl = this.gl; + + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); +}; + +/** + * Binds the buffers to the GPU + * + */ +ParticleBuffer.prototype.bind = function () +{ + var gl = this.gl; + var i, property; + + this.vao.bind(); +}; + +/** + * Destroys the ParticleBuffer. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); +}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/index.js b/src/index.js index aa515c8..3791291 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); +core.particles = require('./particles'); core.accessibility = require('./accessibility'); // export a premade loader instance diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js new file mode 100644 index 0000000..1af1cc2 --- /dev/null +++ b/src/particles/ParticleContainer.js @@ -0,0 +1,331 @@ +var core = require('../core'); + +/** + * The ParticleContainer class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced + * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new ParticleContainer(); + * + * for (var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @extends PIXI.Container + * @memberof PIXI + * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. + * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. + * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. + * @param [properties.position=true] {boolean} When true, position be uploaded and applied. + * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. + * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. + * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. + * @param [batchSize=15000] {number} Number of particles per batch. + */ +function ParticleContainer(maxSize, properties, batchSize) +{ + core.Container.call(this); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); +} + +ParticleContainer.prototype = Object.create(core.Container.prototype); +ParticleContainer.prototype.constructor = ParticleContainer; +module.exports = ParticleContainer; + +/** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ +ParticleContainer.prototype.setProperties = function(properties) +{ + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; + } +}; + +/** + * Updates the object transform for rendering + * + * @private + */ +ParticleContainer.prototype.updateTransform = function () +{ + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ +ParticleContainer.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + + if(!this.baseTexture) + { + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) + { + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this) + } + } + + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Set the flag that static data should be updated to true + * + * @private + */ +ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) +{ + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ +ParticleContainer.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + positionX, + positionY, + finalWidth, + finalHeight + ); + } +}; + +/** + * Destroys the container + * + * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well + */ +ParticleContainer.prototype.destroy = function () { + Container.prototype.destroy.apply(this, arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; +}; diff --git a/src/particles/index.js b/src/particles/index.js new file mode 100644 index 0000000..4a93acb --- /dev/null +++ b/src/particles/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI extras library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/pixijs/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI.mesh + */ +module.exports = { + ParticleContainer: require('./ParticleContainer'), + ParticleRenderer: require('./webgl/ParticleRenderer') +}; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..ba79181 --- /dev/null +++ b/src/particles/webgl/ParticleBuffer.js @@ -0,0 +1,226 @@ +var glCore = require('pixi-gl-core'), + createIndicesForQuads = require('../../core/utils/createIndicesForQuads'); + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers 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 + */ + +/** + * The particle buffer manages the static and dynamic buffers for a particle container. + * + * @class + * @private + * @memberof PIXI + */ +function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) +{ + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; + + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) + { + var property = properties[i]; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } + } + + 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 + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size) + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); + } +}; + +/** + * Uploads the dynamic properties. + * + */ +ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) +{ + var gl = this.gl; + + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } + + this.dynamicBuffer.upload(); +}; + +/** + * Uploads the static properties. + * + */ +ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) +{ + var gl = this.gl; + + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); +}; + +/** + * Binds the buffers to the GPU + * + */ +ParticleBuffer.prototype.bind = function () +{ + var gl = this.gl; + var i, property; + + this.vao.bind(); +}; + +/** + * Destroys the ParticleBuffer. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); +}; diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..787e0c8 --- /dev/null +++ b/src/particles/webgl/ParticleRenderer.js @@ -0,0 +1,428 @@ +var core = require('../../core'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'); + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers 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 + * @memberof PIXI + * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + core.ObjectRenderer.call(this, renderer); + + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + // and max number of element in the index buffer is 16384 * 6 = 98304 + // Creating a full index buffer, overhead is 98304 * 2 = 196Ko + var numIndices = 98304; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + this.indexBuffer = null; + + this.properties = null; + + this.tempMatrix = new core.math.Matrix(); + + this.CONTEXT_UID = 0; +} + +ParticleRenderer.prototype = Object.create(core.ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +core.WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * When there is a WebGL context change + * + * @private + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // setup default shader + this.shader = new ParticleShader(gl); + + this.properties = [ + // verticesData + { + attribute:this.shader.attributes.aVertexPosition, + size:2, + uploadFunction:this.uploadVertices, + offset:0 + }, + // positionData + { + attribute:this.shader.attributes.aPositionCoord, + size:2, + uploadFunction:this.uploadPosition, + offset:0 + }, + // rotationData + { + attribute:this.shader.attributes.aRotation, + size:1, + uploadFunction:this.uploadRotation, + offset:0 + }, + // uvsData + { + attribute:this.shader.attributes.aTextureCoord, + size:2, + uploadFunction:this.uploadUvs, + offset:0 + }, + // alphaData + { + attribute:this.shader.attributes.aColor, + size:1, + uploadFunction:this.uploadAlpha, + offset:0 + } + ]; + +}; + +/** + * Starts a new particle batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + this.renderer.bindShader(this.shader); +}; + + +/** + * Renders the particle container object. + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ +ParticleRenderer.prototype.render = function (container) +{ + var children = container.children, + totalChildren = children.length, + maxSize = container._maxSize, + batchSize = container._batchSize; + + if(totalChildren === 0) + { + return; + } + else if(totalChildren > maxSize) + { + totalChildren = maxSize; + } + + var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; + + if(!buffers) + { + buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); + } + + // if the uvs have not updated then no point rendering just yet! + this.renderer.setBlendMode(container.blendMode); + + var gl = this.renderer.gl; + + // var m = container.worldTransform.copy( this.tempMatrix ); +// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + this.shader.uniforms.uAlpha = container.worldAlpha; + + + // make sure the texture is bound.. + var baseTexture = children[0]._texture.baseTexture; + + this.renderer.bindTexture(baseTexture); + + // now lets upload and render the buffers.. + for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) + { + var amount = ( totalChildren - i); + if(amount > batchSize) + { + amount = batchSize; + } + + var buffer = buffers[j]; + + // we always upload the dynamic + buffer.uploadDynamic(children, i, amount); + + // we only upload the static content when we have to! + if(container._bufferToUpdate === j) + { + buffer.uploadStatic(children, i, amount); + container._bufferToUpdate = j + 1; + } + + // bind the buffer + buffer.vao.bind( this.shader ) + .draw(gl.TRIANGLES, amount * 6) + .unbind(); + + // now draw those suckas! + // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); + // this.renderer.drawCount++; + } +}; + +/** + * Creates one particle buffer for each child in the container we want to render and updates internal properties + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ +ParticleRenderer.prototype.generateBuffers = function (container) +{ + var gl = this.renderer.gl, + buffers = [], + size = container._maxSize, + batchSize = container._batchSize, + dynamicPropertyFlags = container._properties, + i; + + for (i = 0; i < size; i += batchSize) + { + buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); + } + + return buffers; +}; + +/** + * Uploads the verticies. + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their vertices uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +ParticleRenderer.prototype.uploadVertices = 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; + var trim = texture.trim, crop = texture.crop; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; + + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; + + } + else + { + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; + + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.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; + } + +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their positions uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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; + } + +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their rotation uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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; + } +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their Uvs uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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; + } + } +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their alpha uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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 ParticleRenderer. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + if (this.renderer.gl) { + this.renderer.gl.deleteBuffer(this.indexBuffer); + } + + ObjectRenderer.prototype.destroy.apply(this, arguments); + + this.shader.destroy(); + + this.indices = null; + this.tempMatrix = null; +}; diff --git a/src/core/index.js b/src/core/index.js index 37f11db..689b18d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -21,9 +21,7 @@ // sprites Sprite: require('./sprites/Sprite'), CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), - ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // text Text: require('./text/Text'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js deleted file mode 100644 index e992580..0000000 --- a/src/core/particles/ParticleContainer.js +++ /dev/null @@ -1,332 +0,0 @@ -var Container = require('../display/Container'), - CONST = require('../const'); - -/** - * The ParticleContainer class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced - * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new ParticleContainer(); - * - * for (var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @extends PIXI.Container - * @memberof PIXI - * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. - * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. - * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. - * @param [properties.position=true] {boolean} When true, position be uploaded and applied. - * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. - * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. - * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. - * @param [batchSize=15000] {number} Number of particles per batch. - */ -function ParticleContainer(maxSize, properties, batchSize) -{ - Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - - if(!this.baseTexture) - { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this) - } - } - - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; - - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); - - finalWidth = frame.width; - finalHeight = frame.height; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - positionX, - positionY, - finalWidth, - finalHeight - ); - } -}; - -/** - * Destroys the container - * - * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well - */ -ParticleContainer.prototype.destroy = function () { - Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); - } - } - - this._properties = null; - this._buffers = null; -}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js deleted file mode 100644 index 2bfc4ea..0000000 --- a/src/core/particles/webgl/ParticleBuffer.js +++ /dev/null @@ -1,226 +0,0 @@ -var glCore = require('pixi-gl-core'), - createIndicesForQuads = require('../../utils/createIndicesForQuads'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers 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 - */ - -/** - * The particle buffer manages the static and dynamic buffers for a particle container. - * - * @class - * @private - * @memberof PIXI - */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) - { - var property = properties[i]; - - if(dynamicPropertyFlags[i]) - { - this.dynamicProperties.push(property); - } - else - { - this.staticProperties.push(property); - } - } - - 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 - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size) - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - var gl = this.gl; - - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - var gl = this.gl; - var i, property; - - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js deleted file mode 100644 index 8fc97c6..0000000 --- a/src/core/particles/webgl/ParticleRenderer.js +++ /dev/null @@ -1,430 +0,0 @@ -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 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 - * @memberof PIXI - * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. - */ -function ParticleRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - var numIndices = 98304; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; - - this.indexBuffer = null; - - this.properties = null; - - this.tempMatrix = new math.Matrix(); - - this.CONTEXT_UID = 0; -} - -ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; -module.exports = ParticleRenderer; - -WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - // var m = container.worldTransform.copy( this.tempMatrix ); -// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind( this.shader ) - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = 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; - var trim = texture.trim, crop = texture.crop; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * crop.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * crop.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (crop.width ) * (1-sprite.anchor.x); - w1 = (crop.width ) * -sprite.anchor.x; - - h0 = crop.height * (1-sprite.anchor.y); - h1 = crop.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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -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 ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - - ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js deleted file mode 100644 index 950e612..0000000 --- a/src/core/particles/webgl/ParticleShader.js +++ /dev/null @@ -1,66 +0,0 @@ -var Shader = require('pixi-gl-core').GLShader; - -/** - * @class - * @extends PIXI.TextureShader - * @memberof PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); - - // TEMP HACK - -} - -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - -module.exports = ParticleShader; diff --git a/src/index.js b/src/index.js index aa515c8..3791291 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); +core.particles = require('./particles'); core.accessibility = require('./accessibility'); // export a premade loader instance diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js new file mode 100644 index 0000000..1af1cc2 --- /dev/null +++ b/src/particles/ParticleContainer.js @@ -0,0 +1,331 @@ +var core = require('../core'); + +/** + * The ParticleContainer class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that advanced + * functionality will not work. ParticleContainer implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new ParticleContainer(); + * + * for (var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @extends PIXI.Container + * @memberof PIXI + * @param [maxSize=15000] {number} The maximum number of particles that can be renderer by the container. + * @param [properties] {object} The properties of children that should be uploaded to the gpu and applied. + * @param [properties.scale=false] {boolean} When true, scale be uploaded and applied. + * @param [properties.position=true] {boolean} When true, position be uploaded and applied. + * @param [properties.rotation=false] {boolean} When true, rotation be uploaded and applied. + * @param [properties.uvs=false] {boolean} When true, uvs be uploaded and applied. + * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. + * @param [batchSize=15000] {number} Number of particles per batch. + */ +function ParticleContainer(maxSize, properties, batchSize) +{ + core.Container.call(this); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); +} + +ParticleContainer.prototype = Object.create(core.Container.prototype); +ParticleContainer.prototype.constructor = ParticleContainer; +module.exports = ParticleContainer; + +/** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ +ParticleContainer.prototype.setProperties = function(properties) +{ + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; + } +}; + +/** + * Updates the object transform for rendering + * + * @private + */ +ParticleContainer.prototype.updateTransform = function () +{ + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ +ParticleContainer.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + + if(!this.baseTexture) + { + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) + { + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this) + } + } + + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Set the flag that static data should be updated to true + * + * @private + */ +ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) +{ + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ +ParticleContainer.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + positionX, + positionY, + finalWidth, + finalHeight + ); + } +}; + +/** + * Destroys the container + * + * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well + */ +ParticleContainer.prototype.destroy = function () { + Container.prototype.destroy.apply(this, arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; +}; diff --git a/src/particles/index.js b/src/particles/index.js new file mode 100644 index 0000000..4a93acb --- /dev/null +++ b/src/particles/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI extras library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/pixijs/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI.mesh + */ +module.exports = { + ParticleContainer: require('./ParticleContainer'), + ParticleRenderer: require('./webgl/ParticleRenderer') +}; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..ba79181 --- /dev/null +++ b/src/particles/webgl/ParticleBuffer.js @@ -0,0 +1,226 @@ +var glCore = require('pixi-gl-core'), + createIndicesForQuads = require('../../core/utils/createIndicesForQuads'); + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers 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 + */ + +/** + * The particle buffer manages the static and dynamic buffers for a particle container. + * + * @class + * @private + * @memberof PIXI + */ +function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) +{ + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; + + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) + { + var property = properties[i]; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } + } + + 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 + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size) + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); + } +}; + +/** + * Uploads the dynamic properties. + * + */ +ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) +{ + var gl = this.gl; + + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } + + this.dynamicBuffer.upload(); +}; + +/** + * Uploads the static properties. + * + */ +ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) +{ + var gl = this.gl; + + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); +}; + +/** + * Binds the buffers to the GPU + * + */ +ParticleBuffer.prototype.bind = function () +{ + var gl = this.gl; + var i, property; + + this.vao.bind(); +}; + +/** + * Destroys the ParticleBuffer. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); +}; diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..787e0c8 --- /dev/null +++ b/src/particles/webgl/ParticleRenderer.js @@ -0,0 +1,428 @@ +var core = require('../../core'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'); + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers 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 + * @memberof PIXI + * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + core.ObjectRenderer.call(this, renderer); + + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + // and max number of element in the index buffer is 16384 * 6 = 98304 + // Creating a full index buffer, overhead is 98304 * 2 = 196Ko + var numIndices = 98304; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + this.indexBuffer = null; + + this.properties = null; + + this.tempMatrix = new core.math.Matrix(); + + this.CONTEXT_UID = 0; +} + +ParticleRenderer.prototype = Object.create(core.ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +core.WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * When there is a WebGL context change + * + * @private + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // setup default shader + this.shader = new ParticleShader(gl); + + this.properties = [ + // verticesData + { + attribute:this.shader.attributes.aVertexPosition, + size:2, + uploadFunction:this.uploadVertices, + offset:0 + }, + // positionData + { + attribute:this.shader.attributes.aPositionCoord, + size:2, + uploadFunction:this.uploadPosition, + offset:0 + }, + // rotationData + { + attribute:this.shader.attributes.aRotation, + size:1, + uploadFunction:this.uploadRotation, + offset:0 + }, + // uvsData + { + attribute:this.shader.attributes.aTextureCoord, + size:2, + uploadFunction:this.uploadUvs, + offset:0 + }, + // alphaData + { + attribute:this.shader.attributes.aColor, + size:1, + uploadFunction:this.uploadAlpha, + offset:0 + } + ]; + +}; + +/** + * Starts a new particle batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + this.renderer.bindShader(this.shader); +}; + + +/** + * Renders the particle container object. + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ +ParticleRenderer.prototype.render = function (container) +{ + var children = container.children, + totalChildren = children.length, + maxSize = container._maxSize, + batchSize = container._batchSize; + + if(totalChildren === 0) + { + return; + } + else if(totalChildren > maxSize) + { + totalChildren = maxSize; + } + + var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; + + if(!buffers) + { + buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); + } + + // if the uvs have not updated then no point rendering just yet! + this.renderer.setBlendMode(container.blendMode); + + var gl = this.renderer.gl; + + // var m = container.worldTransform.copy( this.tempMatrix ); +// m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + this.shader.uniforms.uAlpha = container.worldAlpha; + + + // make sure the texture is bound.. + var baseTexture = children[0]._texture.baseTexture; + + this.renderer.bindTexture(baseTexture); + + // now lets upload and render the buffers.. + for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) + { + var amount = ( totalChildren - i); + if(amount > batchSize) + { + amount = batchSize; + } + + var buffer = buffers[j]; + + // we always upload the dynamic + buffer.uploadDynamic(children, i, amount); + + // we only upload the static content when we have to! + if(container._bufferToUpdate === j) + { + buffer.uploadStatic(children, i, amount); + container._bufferToUpdate = j + 1; + } + + // bind the buffer + buffer.vao.bind( this.shader ) + .draw(gl.TRIANGLES, amount * 6) + .unbind(); + + // now draw those suckas! + // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); + // this.renderer.drawCount++; + } +}; + +/** + * Creates one particle buffer for each child in the container we want to render and updates internal properties + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ +ParticleRenderer.prototype.generateBuffers = function (container) +{ + var gl = this.renderer.gl, + buffers = [], + size = container._maxSize, + batchSize = container._batchSize, + dynamicPropertyFlags = container._properties, + i; + + for (i = 0; i < size; i += batchSize) + { + buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); + } + + return buffers; +}; + +/** + * Uploads the verticies. + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their vertices uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +ParticleRenderer.prototype.uploadVertices = 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; + var trim = texture.trim, crop = texture.crop; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; + + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; + + } + else + { + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; + + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.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; + } + +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their positions uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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; + } + +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their rotation uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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; + } +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their Uvs uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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; + } + } +}; + +/** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their alpha uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ +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 ParticleRenderer. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + if (this.renderer.gl) { + this.renderer.gl.deleteBuffer(this.indexBuffer); + } + + ObjectRenderer.prototype.destroy.apply(this, arguments); + + this.shader.destroy(); + + this.indices = null; + this.tempMatrix = null; +}; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js new file mode 100644 index 0000000..950e612 --- /dev/null +++ b/src/particles/webgl/ParticleShader.js @@ -0,0 +1,66 @@ +var Shader = require('pixi-gl-core').GLShader; + +/** + * @class + * @extends PIXI.TextureShader + * @memberof PIXI + * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. + */ +function ParticleShader(gl) +{ + Shader.call(this, + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', + + 'uniform mat3 projectionMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'void main(void){', + ' vec2 v = aVertexPosition;', + + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', + + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'uniform sampler2D uSampler;', + 'uniform float uAlpha;', + + 'void main(void){', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' if (color.a == 0.0) discard;', + ' gl_FragColor = color;', + '}' + ].join('\n') + ); + + // TEMP HACK + +} + +ParticleShader.prototype = Object.create(Shader.prototype); +ParticleShader.prototype.constructor = ParticleShader; + +module.exports = ParticleShader;