1 /**
  2  * Copyright (c) 2008-2010 Ricardo Quesada
  3  * Copyright (c) 2011-2012 cocos2d-x.org
  4  * Copyright (c) 2013-2014 Chukong Technologies Inc.
  5  * Copyright (C) 2009 Matt Oswald
  6  * Copyright (c) 2011 Marco Tillemans
  7  *
  8  * http://www.cocos2d-x.org
  9  *
 10  * Permission is hereby granted, free of charge, to any person obtaining a copy
 11  * of this software and associated documentation files (the "Software"), to deal
 12  * in the Software without restriction, including without limitation the rights
 13  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 14  * copies of the Software, and to permit persons to whom the Software is
 15  * furnished to do so, subject to the following conditions:
 16  *
 17  * The above copyright notice and this permission notice shall be included in
 18  * all copies or substantial portions of the Software.
 19  *
 20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 25  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 26  * THE SOFTWARE.
 27  *
 28  */
 29 
 30 /**
 31  * paticle default capacity
 32  * @constant
 33  * @type Number
 34  */
 35 cc.PARTICLE_DEFAULT_CAPACITY = 500;
 36 
 37 /**
 38  * <p>
 39  *    cc.ParticleBatchNode is like a batch node: if it contains children, it will draw them in 1 single OpenGL call  <br/>
 40  *    (often known as "batch draw").  </br>
 41  *
 42  *    A cc.ParticleBatchNode can reference one and only one texture (one image file, one texture atlas).<br/>
 43  *    Only the cc.ParticleSystems that are contained in that texture can be added to the cc.SpriteBatchNode.<br/>
 44  *    All cc.ParticleSystems added to a cc.SpriteBatchNode are drawn in one OpenGL ES draw call.<br/>
 45  *    If the cc.ParticleSystems are not added to a cc.ParticleBatchNode then an OpenGL ES draw call will be needed for each one, which is less efficient.</br>
 46  *
 47  *    Limitations:<br/>
 48  *    - At the moment only cc.ParticleSystem is supported<br/>
 49  *    - All systems need to be drawn with the same parameters, blend function, aliasing, texture<br/>
 50  *
 51  *    Most efficient usage<br/>
 52  *    - Initialize the ParticleBatchNode with the texture and enough capacity for all the particle systems<br/>
 53  *    - Initialize all particle systems and add them as child to the batch node<br/>
 54  * </p>
 55  * @class
 56  * @extends cc.ParticleSystem
 57  * @param {String|cc.Texture2D} fileImage
 58  * @param {Number} capacity
 59  *
 60  * @property {cc.Texture2D|HTMLImageElement|HTMLCanvasElement}  texture         - The used texture
 61  * @property {cc.TextureAtlas}                                  textureAtlas    - The texture atlas used for drawing the quads
 62  *
 63  * @example
 64  * 1.
 65  * //Create a cc.ParticleBatchNode with image path  and capacity
 66  * var particleBatchNode = new cc.ParticleBatchNode("res/grossini_dance.png",30);
 67  *
 68  * 2.
 69  * //Create a cc.ParticleBatchNode with a texture and capacity
 70  * var texture = cc.TextureCache.getInstance().addImage("res/grossini_dance.png");
 71  * var particleBatchNode = new cc.ParticleBatchNode(texture, 30);
 72  */
 73 cc.ParticleBatchNode = cc.Node.extend(/** @lends cc.ParticleBatchNode# */{
 74 	textureAtlas:null,
 75 
 76     TextureProtocol:true,
 77     //the blend function used for drawing the quads
 78     _blendFunc:null,
 79     _className:"ParticleBatchNode",
 80 
 81     /**
 82      * initializes the particle system with the name of a file on disk (for a list of supported formats look at the cc.Texture2D class), a capacity of particles
 83      * Constructor of cc.ParticleBatchNode
 84      * @param {String|cc.Texture2D} fileImage
 85      * @param {Number} capacity
 86      * @example
 87      * 1.
 88      * //Create a cc.ParticleBatchNode with image path  and capacity
 89      * var particleBatchNode = new cc.ParticleBatchNode("res/grossini_dance.png",30);
 90      *
 91      * 2.
 92      * //Create a cc.ParticleBatchNode with a texture and capacity
 93      * var texture = cc.TextureCache.getInstance().addImage("res/grossini_dance.png");
 94      * var particleBatchNode = new cc.ParticleBatchNode(texture, 30);
 95      */
 96     ctor:function (fileImage, capacity) {
 97         cc.Node.prototype.ctor.call(this);
 98         this._blendFunc = {src:cc.BLEND_SRC, dst:cc.BLEND_DST};
 99         if (cc.isString(fileImage)) {
100             this.init(fileImage, capacity);
101         } else if (fileImage instanceof cc.Texture2D) {
102             this.initWithTexture(fileImage, capacity);
103         }
104     },
105 
106     /**
107      * initializes the particle system with cc.Texture2D, a capacity of particles
108      * @param {cc.Texture2D|HTMLImageElement|HTMLCanvasElement} texture
109      * @param {Number} capacity
110      * @return {Boolean}
111      */
112     initWithTexture:function (texture, capacity) {
113         this.textureAtlas = new cc.TextureAtlas();
114         this.textureAtlas.initWithTexture(texture, capacity);
115 
116         // no lazy alloc in this node
117         this._children.length = 0;
118 
119         if (cc._renderType === cc._RENDER_TYPE_WEBGL)
120             this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR);
121         return true;
122     },
123 
124     /**
125      * initializes the particle system with the name of a file on disk (for a list of supported formats look at the cc.Texture2D class), a capacity of particles
126      * @param {String} fileImage
127      * @param {Number} capacity
128      * @return {Boolean}
129      */
130     initWithFile:function (fileImage, capacity) {
131         var tex = cc.textureCache.addImage(fileImage);
132         return this.initWithTexture(tex, capacity);
133     },
134 
135     /**
136      * initializes the particle system with the name of a file on disk (for a list of supported formats look at the cc.Texture2D class), a capacity of particles
137      * @param {String} fileImage
138      * @param {Number} capacity
139      * @return {Boolean}
140      */
141     init:function (fileImage, capacity) {
142         var tex = cc.TextureCache.getInstance().addImage(fileImage);
143         return this.initWithTexture(tex, capacity);
144     },
145 
146     /**
147      * Add a child into the cc.ParticleBatchNode
148      * @param {cc.ParticleSystem} child
149      * @param {Number} zOrder
150      * @param {Number} tag
151      */
152     addChild:function (child, zOrder, tag) {
153         if(!child)
154             throw "cc.ParticleBatchNode.addChild() : child should be non-null";
155         if(!(child instanceof cc.ParticleSystem))
156             throw "cc.ParticleBatchNode.addChild() : only supports cc.ParticleSystem as children";
157         zOrder = (zOrder == null) ? child.zIndex : zOrder;
158         tag = (tag == null) ? child.tag : tag;
159 
160         if(child.getTexture() != this.textureAtlas.texture)
161             throw "cc.ParticleSystem.addChild() : the child is not using the same texture id";
162 
163         // If this is the 1st children, then copy blending function
164         var childBlendFunc = child.getBlendFunc();
165         if (this._children.length === 0)
166             this.setBlendFunc(childBlendFunc);
167         else{
168             if((childBlendFunc.src != this._blendFunc.src) || (childBlendFunc.dst != this._blendFunc.dst)){
169                 cc.log("cc.ParticleSystem.addChild() : Can't add a ParticleSystem that uses a different blending function");
170                 return;
171             }
172         }
173 
174         //no lazy sorting, so don't call super addChild, call helper instead
175         var pos = this._addChildHelper(child, zOrder, tag);
176 
177         //get new atlasIndex
178         var atlasIndex = 0;
179 
180         if (pos != 0) {
181             var p = this._children[pos - 1];
182             atlasIndex = p.getAtlasIndex() + p.getTotalParticles();
183         } else
184             atlasIndex = 0;
185 
186         this.insertChild(child, atlasIndex);
187 
188         // update quad info
189         child.setBatchNode(this);
190     },
191 
192     /**
193      * Inserts a child into the cc.ParticleBatchNode
194      * @param {cc.ParticleSystem} pSystem
195      * @param {Number} index
196      */
197     insertChild:function (pSystem, index) {
198         var totalParticles = pSystem.getTotalParticles();
199         var locTextureAtlas = this.textureAtlas;
200         var totalQuads = locTextureAtlas.totalQuads;
201         pSystem.setAtlasIndex(index);
202         if (totalQuads + totalParticles > locTextureAtlas.getCapacity()) {
203             this._increaseAtlasCapacityTo(totalQuads + totalParticles);
204             // after a realloc empty quads of textureAtlas can be filled with gibberish (realloc doesn't perform calloc), insert empty quads to prevent it
205             locTextureAtlas.fillWithEmptyQuadsFromIndex(locTextureAtlas.getCapacity() - totalParticles, totalParticles);
206         }
207 
208         // make room for quads, not necessary for last child
209         if (pSystem.getAtlasIndex() + totalParticles != totalQuads)
210             locTextureAtlas.moveQuadsFromIndex(index, index + totalParticles);
211 
212         // increase totalParticles here for new particles, update method of particlesystem will fill the quads
213         locTextureAtlas.increaseTotalQuadsWith(totalParticles);
214         this._updateAllAtlasIndexes();
215     },
216 
217     /**
218      * @param {cc.ParticleSystem} child
219      * @param {Boolean} cleanup
220      */
221     removeChild:function (child, cleanup) {
222         // explicit nil handling
223         if (child == null)
224             return;
225 
226         if(!(child instanceof cc.ParticleSystem))
227             throw "cc.ParticleBatchNode.removeChild(): only supports cc.ParticleSystem as children";
228         if(this._children.indexOf(child) == -1){
229             cc.log("cc.ParticleBatchNode.removeChild(): doesn't contain the sprite. Can't remove it");
230             return;
231         }
232 
233         cc.Node.prototype.removeChild.call(this, child, cleanup);
234 
235         var locTextureAtlas = this.textureAtlas;
236         // remove child helper
237         locTextureAtlas.removeQuadsAtIndex(child.getAtlasIndex(), child.getTotalParticles());
238 
239         // after memmove of data, empty the quads at the end of array
240         locTextureAtlas.fillWithEmptyQuadsFromIndex(locTextureAtlas.totalQuads, child.getTotalParticles());
241 
242         // paticle could be reused for self rendering
243         child.setBatchNode(null);
244 
245         this._updateAllAtlasIndexes();
246     },
247 
248     /**
249      * Reorder will be done in this function, no "lazy" reorder to particles
250      * @param {cc.ParticleSystem} child
251      * @param {Number} zOrder
252      */
253     reorderChild:function (child, zOrder) {
254         if(!child)
255             throw "cc.ParticleBatchNode.reorderChild(): child should be non-null";
256         if(!(child instanceof cc.ParticleSystem))
257             throw "cc.ParticleBatchNode.reorderChild(): only supports cc.QuadParticleSystems as children";
258         if(this._children.indexOf(child) === -1){
259             cc.log("cc.ParticleBatchNode.reorderChild(): Child doesn't belong to batch");
260             return;
261         }
262 
263         if (zOrder == child.zIndex)
264             return;
265 
266         // no reordering if only 1 child
267         if (this._children.length > 1) {
268             var getIndexes = this._getCurrentIndex(child, zOrder);
269 
270             if (getIndexes.oldIndex != getIndexes.newIndex) {
271                 // reorder m_pChildren.array
272                 this._children.splice(getIndexes.oldIndex, 1)
273                 this._children.splice(getIndexes.newIndex, 0, child);
274 
275                 // save old altasIndex
276                 var oldAtlasIndex = child.getAtlasIndex();
277 
278                 // update atlas index
279                 this._updateAllAtlasIndexes();
280 
281                 // Find new AtlasIndex
282                 var newAtlasIndex = 0;
283                 var locChildren = this._children;
284                 for (var i = 0; i < locChildren.length; i++) {
285                     var pNode = locChildren[i];
286                     if (pNode == child) {
287                         newAtlasIndex = child.getAtlasIndex();
288                         break;
289                     }
290                 }
291 
292                 // reorder textureAtlas quads
293                 this.textureAtlas.moveQuadsFromIndex(oldAtlasIndex, child.getTotalParticles(), newAtlasIndex);
294 
295                 child.updateWithNoTime();
296             }
297         }
298         child._setLocalZOrder(zOrder);
299     },
300 
301     /**
302      * @param {Number} index
303      * @param {Boolean} doCleanup
304      */
305     removeChildAtIndex:function (index, doCleanup) {
306         this.removeChild(this._children[i], doCleanup);
307     },
308 
309     /**
310      * @param {Boolean} doCleanup
311      */
312     removeAllChildren:function (doCleanup) {
313         var locChildren = this._children;
314         for (var i = 0; i < locChildren.length; i++) {
315             locChildren[i].setBatchNode(null);
316         }
317         cc.Node.prototype.removeAllChildren.call(this, doCleanup);
318         this.textureAtlas.removeAllQuads();
319     },
320 
321     /**
322      * disables a particle by inserting a 0'd quad into the texture atlas
323      * @param {Number} particleIndex
324      */
325     disableParticle:function (particleIndex) {
326         var quad = this.textureAtlas.quads[particleIndex];
327         quad.br.vertices.x = quad.br.vertices.y = quad.tr.vertices.x = quad.tr.vertices.y =
328             quad.tl.vertices.x = quad.tl.vertices.y = quad.bl.vertices.x = quad.bl.vertices.y = 0.0;
329         this.textureAtlas._setDirty(true);
330     },
331 
332     /**
333      * Render function using the canvas 2d context or WebGL context, internal usage only, please do not call this function
334      * @function
335      * @param {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context
336      */
337     draw:function (ctx) {
338         //cc.PROFILER_STOP("CCParticleBatchNode - draw");
339         if (cc._renderType === cc._RENDER_TYPE_CANVAS)
340             return;
341 
342         if (this.textureAtlas.totalQuads == 0)
343             return;
344 
345         cc.nodeDrawSetup(this);
346         cc.glBlendFuncForParticle(this._blendFunc.src, this._blendFunc.dst);
347         this.textureAtlas.drawQuads();
348 
349         //cc.PROFILER_STOP("CCParticleBatchNode - draw");
350     },
351 
352     /**
353      * returns the used texture
354      * @return {cc.Texture2D|HTMLImageElement|HTMLCanvasElement}
355      */
356     getTexture:function () {
357         return this.textureAtlas.texture;
358     },
359 
360     /**
361      * sets a new texture. it will be retained
362      * @param {cc.Texture2D|HTMLImageElement|HTMLCanvasElement} texture
363      */
364     setTexture:function (texture) {
365         this.textureAtlas.texture = texture;
366 
367         // If the new texture has No premultiplied alpha, AND the blendFunc hasn't been changed, then update it
368         var locBlendFunc = this._blendFunc;
369         if (texture && !texture.hasPremultipliedAlpha() && ( locBlendFunc.src == cc.BLEND_SRC && locBlendFunc.dst == cc.BLEND_DST )) {
370             locBlendFunc.src = cc.SRC_ALPHA;
371             locBlendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
372         }
373     },
374 
375     /**
376      * set the blending function used for the texture
377      * @param {Number|Object} src
378      * @param {Number} dst
379      */
380     setBlendFunc:function (src, dst) {
381         if (dst === undefined){
382             this._blendFunc.src = src.src;
383             this._blendFunc.dst = src.dst;
384         } else{
385             this._blendFunc.src = src;
386             this._blendFunc.src = dst;
387         }
388 
389     },
390 
391     /**
392      * returns the blending function used for the texture
393      * @return {cc.BlendFunc}
394      */
395     getBlendFunc:function () {
396         return {src:this._blendFunc.src, dst:this._blendFunc.dst};
397     },
398 
399     // override visit.
400     // Don't call visit on it's children
401     /**
402      * Recursive method that visit its children and draw them
403      * @function
404      * @param {CanvasRenderingContext2D|WebGLRenderingContext} ctx
405      */
406     visit:function (ctx) {
407         if (cc._renderType === cc._RENDER_TYPE_CANVAS)
408             return;
409 
410         // CAREFUL:
411         // This visit is almost identical to cc.Node#visit
412         // with the exception that it doesn't call visit on it's children
413         //
414         // The alternative is to have a void cc.Sprite#visit, but
415         // although this is less mantainable, is faster
416         //
417         if (!this._visible)
418             return;
419 
420         cc.kmGLPushMatrix();
421         if (this.grid && this.grid.isActive()) {
422             this.grid.beforeDraw();
423             this.transformAncestors();
424         }
425 
426         this.transform(ctx);
427         this.draw(ctx);
428 
429         if (this.grid && this.grid.isActive())
430             this.grid.afterDraw(this);
431 
432         cc.kmGLPopMatrix();
433     },
434 
435     _updateAllAtlasIndexes:function () {
436         var index = 0;
437         var locChildren = this._children;
438         for (var i = 0; i < locChildren.length; i++) {
439             var child = locChildren[i];
440             child.setAtlasIndex(index);
441             index += child.getTotalParticles();
442         }
443     },
444 
445     _increaseAtlasCapacityTo:function (quantity) {
446         cc.log("cocos2d: cc.ParticleBatchNode: resizing TextureAtlas capacity from [" + this.textureAtlas.getCapacity()
447             + "] to [" + quantity + "].");
448 
449         if (!this.textureAtlas.resizeCapacity(quantity)) {
450             // serious problems
451             cc.log("cc.ParticleBatchNode._increaseAtlasCapacityTo() : WARNING: Not enough memory to resize the atlas");
452         }
453     },
454 
455     _searchNewPositionInChildrenForZ:function (z) {
456         var locChildren = this._children;
457         var count = locChildren.length;
458         for (var i = 0; i < count; i++) {
459             if (locChildren[i].zIndex > z)
460                 return i;
461         }
462         return count;
463     },
464 
465     _getCurrentIndex:function (child, z) {
466         var foundCurrentIdx = false;
467         var foundNewIdx = false;
468 
469         var newIndex = 0;
470         var oldIndex = 0;
471 
472         var minusOne = 0, locChildren = this._children;
473         var count = locChildren.length;
474         for (var i = 0; i < count; i++) {
475             var pNode = locChildren[i];
476             // new index
477             if (pNode.zIndex > z && !foundNewIdx) {
478                 newIndex = i;
479                 foundNewIdx = true;
480 
481                 if (foundCurrentIdx && foundNewIdx)
482                     break;
483             }
484             // current index
485             if (child == pNode) {
486                 oldIndex = i;
487                 foundCurrentIdx = true;
488                 if (!foundNewIdx)
489                     minusOne = -1;
490                 if (foundCurrentIdx && foundNewIdx)
491                     break;
492             }
493         }
494         if (!foundNewIdx)
495             newIndex = count;
496         newIndex += minusOne;
497         return {newIndex:newIndex, oldIndex:oldIndex};
498     },
499 
500     //
501     // <p>
502     //     don't use lazy sorting, reordering the particle systems quads afterwards would be too complex                                    <br/>
503     //     XXX research whether lazy sorting + freeing current quads and calloc a new block with size of capacity would be faster           <br/>
504     //     XXX or possibly using vertexZ for reordering, that would be fastest                                                              <br/>
505     //     this helper is almost equivalent to CCNode's addChild, but doesn't make use of the lazy sorting                                  <br/>
506     // </p>
507     // @param {cc.ParticleSystem} child
508     // @param {Number} z
509     // @param {Number} aTag
510     // @return {Number}
511     // @private
512     //
513     _addChildHelper:function (child, z, aTag) {
514         if(!child)
515             throw "cc.ParticleBatchNode._addChildHelper(): child should be non-null";
516         if(child.parent){
517             cc.log("cc.ParticleBatchNode._addChildHelper(): child already added. It can't be added again");
518             return null;
519         }
520 
521 
522         if (!this._children)
523             this._children = [];
524 
525         //don't use a lazy insert
526         var pos = this._searchNewPositionInChildrenForZ(z);
527 
528         this._children.splice(pos, 0, child);
529         child.tag = aTag;
530         child._setLocalZOrder(z);
531         child.parent = this;
532         if (this._running) {
533             child.onEnter();
534             child.onEnterTransitionDidFinish();
535         }
536         return pos;
537     },
538 
539     _updateBlendFunc:function () {
540         if (!this.textureAtlas.texture.hasPremultipliedAlpha()) {
541             this._blendFunc.src = cc.SRC_ALPHA;
542             this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
543         }
544     },
545 
546     /**
547      * return the texture atlas used for drawing the quads
548      * @return {cc.TextureAtlas}
549      */
550     getTextureAtlas:function () {
551         return this.textureAtlas;
552     },
553 
554     /**
555      * set the texture atlas used for drawing the quads
556      * @param {cc.TextureAtlas} textureAtlas
557      */
558     setTextureAtlas:function (textureAtlas) {
559         this.textureAtlas = textureAtlas;
560     }
561 });
562 
563 var _p = cc.ParticleBatchNode.prototype;
564 
565 // Extended properties
566 /** @expose */
567 _p.texture;
568 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture);
569 
570 
571 /**
572  * initializes the particle system with the name of a file on disk (for a list of supported formats look at the cc.Texture2D class), a capacity of particles
573  * @deprecated since v3.0 please use new cc.ParticleBatchNode(filename, capacity) instead.
574  * @param {String|cc.Texture2D} fileImage
575  * @param {Number} capacity
576  * @return {cc.ParticleBatchNode}
577  * @example
578  * 1.
579  * //Create a cc.ParticleBatchNode with image path  and capacity
580  * var particleBatchNode = cc.ParticleBatchNode.create("res/grossini_dance.png",30);
581  *
582  * 2.
583  * //Create a cc.ParticleBatchNode with a texture and capacity
584  * var texture = cc.TextureCache.getInstance().addImage("res/grossini_dance.png");
585  * var particleBatchNode = cc.ParticleBatchNode.create(texture, 30);
586  */
587 cc.ParticleBatchNode.create = function (fileImage, capacity) {
588     return new cc.ParticleBatchNode(fileImage, capacity);
589 };
590