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 Container * @memberof PIXI * * @param [size=15000] {number} The number of images in the SpriteBatch before it flushes. * @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. */ function ParticleContainer(size, properties) { Container.call(this); /** * Set properties to be dynamic (true) / static (false) * * @member {array} * @private */ this._properties = [false, true, false, false, false]; /** * @member {number} * @private */ this._size = size || 15000; /** * @member {WebGLBuffer} * @private */ this._buffers = null; /** * @member {boolean} * @private */ this._updateStatic = false; /** * @member {boolean} * */ this.interactiveChildren = false; /** * The blend mode to be applied to the sprite. Apply a value of blendModes.NORMAL to reset the blend mode. * * @member {number} * @default CONST.BLEND_MODES.NORMAL; */ 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.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 {WebGLRenderer} The webgl renderer * @private */ ParticleContainer.prototype.renderWebGL = function (renderer) { if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { return; } renderer.setObjectRenderer( renderer.plugins.particle ); renderer.plugins.particle.render( this ); }; /** * Adds a child to this particle container at a specified index. If the index is out of bounds an error will be thrown * * @param child {DisplayObject} The child to add * @param index {Number} The index to place the child in * @return {DisplayObject} The child that was added. */ ParticleContainer.prototype.addChildAt = function (child, index) { // prevent adding self as child if (child === this) { return child; } if (index >= 0 && index <= this.children.length) { if (child.parent) { child.parent.removeChild(child); } child.parent = this; this.children.splice(index, 0, child); this._updateStatic = true; return child; } else { throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); } }; /** * Removes a child from the specified index position. * * @param index {Number} The index to get the child from * @return {DisplayObject} The child that was removed. */ ParticleContainer.prototype.removeChildAt = function (index) { var child = this.getChildAt(index); child.parent = null; this.children.splice(index, 1); this._updateStatic = true; return child; }; /** * Renders the object using the Canvas renderer * * @param renderer {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; 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.destroy(); } } this._properties = null; this._buffers = null; };