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     //the blend function used for drawing the quads
 76     _blendFunc:null,
 77     _className:"ParticleBatchNode",
 78 
 79     /**
 80      * 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
 81      * Constructor of cc.ParticleBatchNode
 82      * @param {String|cc.Texture2D} fileImage
 83      * @param {Number} capacity
 84      * @example
 85      * 1.
 86      * //Create a cc.ParticleBatchNode with image path  and capacity
 87      * var particleBatchNode = new cc.ParticleBatchNode("res/grossini_dance.png",30);
 88      *
 89      * 2.
 90      * //Create a cc.ParticleBatchNode with a texture and capacity
 91      * var texture = cc.TextureCache.getInstance().addImage("res/grossini_dance.png");
 92      * var particleBatchNode = new cc.ParticleBatchNode(texture, 30);
 93      */
 94     ctor:function (fileImage, capacity) {
 95         cc.Node.prototype.ctor.call(this);
 96         this._blendFunc = {src:cc.BLEND_SRC, dst:cc.BLEND_DST};
 97         if (cc.isString(fileImage)) {
 98             this.init(fileImage, capacity);
 99         } else if (fileImage instanceof cc.Texture2D) {
100             this.initWithTexture(fileImage, capacity);
101         }
102     },
103 
104     _createRenderCmd: function(){
105         if(cc._renderType === cc.game.RENDER_TYPE_CANVAS)
106             return new cc.ParticleBatchNode.CanvasRenderCmd(this);
107         else
108             return new cc.ParticleBatchNode.WebGLRenderCmd(this);
109     },
110 
111     /**
112      * initializes the particle system with cc.Texture2D, a capacity of particles
113      * @param {cc.Texture2D|HTMLImageElement|HTMLCanvasElement} texture
114      * @param {Number} capacity
115      * @return {Boolean}
116      */
117     initWithTexture:function (texture, capacity) {
118         this.textureAtlas = new cc.TextureAtlas();
119         this.textureAtlas.initWithTexture(texture, capacity);
120 
121         // no lazy alloc in this node
122         this._children.length = 0;
123 
124         this._renderCmd._initWithTexture();
125         return true;
126     },
127 
128     /**
129      * 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
130      * @param {String} fileImage
131      * @param {Number} capacity
132      * @return {Boolean}
133      */
134     initWithFile:function (fileImage, capacity) {
135         var tex = cc.textureCache.addImage(fileImage);
136         return this.initWithTexture(tex, capacity);
137     },
138 
139     /**
140      * 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
141      * @param {String} fileImage
142      * @param {Number} capacity
143      * @return {Boolean}
144      */
145     init:function (fileImage, capacity) {
146         var tex = cc.textureCache.addImage(fileImage);
147         return this.initWithTexture(tex, capacity);
148     },
149 
150     /**
151      * Add a child into the cc.ParticleBatchNode
152      * @param {cc.ParticleSystem} child
153      * @param {Number} zOrder
154      * @param {Number} tag
155      */
156     addChild:function (child, zOrder, tag) {
157         if(!child)
158             throw new Error("cc.ParticleBatchNode.addChild() : child should be non-null");
159         if(!(child instanceof cc.ParticleSystem))
160             throw new Error("cc.ParticleBatchNode.addChild() : only supports cc.ParticleSystem as children");
161         zOrder = (zOrder == null) ? child.zIndex : zOrder;
162         tag = (tag == null) ? child.tag : tag;
163 
164         if(child.getTexture() !== this.textureAtlas.texture)
165             throw new Error("cc.ParticleSystem.addChild() : the child is not using the same texture id");
166 
167         // If this is the 1st children, then copy blending function
168         var childBlendFunc = child.getBlendFunc();
169         if (this._children.length === 0)
170             this.setBlendFunc(childBlendFunc);
171         else{
172             if((childBlendFunc.src !== this._blendFunc.src) || (childBlendFunc.dst !== this._blendFunc.dst)){
173                 cc.log("cc.ParticleSystem.addChild() : Can't add a ParticleSystem that uses a different blending function");
174                 return;
175             }
176         }
177 
178         //no lazy sorting, so don't call super addChild, call helper instead
179         var pos = this._addChildHelper(child, zOrder, tag);
180 
181         //get new atlasIndex
182         var atlasIndex = 0;
183 
184         if (pos !== 0) {
185             var p = this._children[pos - 1];
186             atlasIndex = p.getAtlasIndex() + p.getTotalParticles();
187         } else
188             atlasIndex = 0;
189 
190         this.insertChild(child, atlasIndex);
191 
192         // update quad info
193         child.setBatchNode(this);
194     },
195 
196     /**
197      * Inserts a child into the cc.ParticleBatchNode
198      * @param {cc.ParticleSystem} pSystem
199      * @param {Number} index
200      */
201     insertChild:function (pSystem, index) {
202         var totalParticles = pSystem.getTotalParticles();
203         var locTextureAtlas = this.textureAtlas;
204         var totalQuads = locTextureAtlas.totalQuads;
205         pSystem.setAtlasIndex(index);
206         if (totalQuads + totalParticles > locTextureAtlas.getCapacity()) {
207             this._increaseAtlasCapacityTo(totalQuads + totalParticles);
208             // after a realloc empty quads of textureAtlas can be filled with gibberish (realloc doesn't perform calloc), insert empty quads to prevent it
209             locTextureAtlas.fillWithEmptyQuadsFromIndex(locTextureAtlas.getCapacity() - totalParticles, totalParticles);
210         }
211 
212         // make room for quads, not necessary for last child
213         if (pSystem.getAtlasIndex() + totalParticles !== totalQuads)
214             locTextureAtlas.moveQuadsFromIndex(index, index + totalParticles);
215 
216         // increase totalParticles here for new particles, update method of particlesystem will fill the quads
217         locTextureAtlas.increaseTotalQuadsWith(totalParticles);
218         this._updateAllAtlasIndexes();
219     },
220 
221     /**
222      * @param {cc.ParticleSystem} child
223      * @param {Boolean} cleanup
224      */
225     removeChild:function (child, cleanup) {
226         // explicit nil handling
227         if (child == null)
228             return;
229 
230         if(!(child instanceof cc.ParticleSystem))
231             throw new Error("cc.ParticleBatchNode.removeChild(): only supports cc.ParticleSystem as children");
232         if(this._children.indexOf(child) === -1){
233             cc.log("cc.ParticleBatchNode.removeChild(): doesn't contain the sprite. Can't remove it");
234             return;
235         }
236 
237         cc.Node.prototype.removeChild.call(this, child, cleanup);
238 
239         var locTextureAtlas = this.textureAtlas;
240         // remove child helper
241         locTextureAtlas.removeQuadsAtIndex(child.getAtlasIndex(), child.getTotalParticles());
242 
243         // after memmove of data, empty the quads at the end of array
244         locTextureAtlas.fillWithEmptyQuadsFromIndex(locTextureAtlas.totalQuads, child.getTotalParticles());
245 
246         // paticle could be reused for self rendering
247         child.setBatchNode(null);
248 
249         this._updateAllAtlasIndexes();
250     },
251 
252     /**
253      * Reorder will be done in this function, no "lazy" reorder to particles
254      * @param {cc.ParticleSystem} child
255      * @param {Number} zOrder
256      */
257     reorderChild:function (child, zOrder) {
258         if(!child)
259             throw new Error("cc.ParticleBatchNode.reorderChild(): child should be non-null");
260         if(!(child instanceof cc.ParticleSystem))
261             throw new Error("cc.ParticleBatchNode.reorderChild(): only supports cc.QuadParticleSystems as children");
262         if(this._children.indexOf(child) === -1){
263             cc.log("cc.ParticleBatchNode.reorderChild(): Child doesn't belong to batch");
264             return;
265         }
266 
267         if (zOrder === child.zIndex)
268             return;
269 
270         // no reordering if only 1 child
271         if (this._children.length > 1) {
272             var getIndexes = this._getCurrentIndex(child, zOrder);
273 
274             if (getIndexes.oldIndex !== getIndexes.newIndex) {
275                 // reorder m_pChildren.array
276                 this._children.splice(getIndexes.oldIndex, 1)
277                 this._children.splice(getIndexes.newIndex, 0, child);
278 
279                 // save old altasIndex
280                 var oldAtlasIndex = child.getAtlasIndex();
281 
282                 // update atlas index
283                 this._updateAllAtlasIndexes();
284 
285                 // Find new AtlasIndex
286                 var newAtlasIndex = 0;
287                 var locChildren = this._children;
288                 for (var i = 0; i < locChildren.length; i++) {
289                     var pNode = locChildren[i];
290                     if (pNode === child) {
291                         newAtlasIndex = child.getAtlasIndex();
292                         break;
293                     }
294                 }
295 
296                 // reorder textureAtlas quads
297                 this.textureAtlas.moveQuadsFromIndex(oldAtlasIndex, child.getTotalParticles(), newAtlasIndex);
298 
299                 child.updateWithNoTime();
300             }
301         }
302         child._setLocalZOrder(zOrder);
303     },
304 
305     /**
306      * @param {Number} index
307      * @param {Boolean} doCleanup
308      */
309     removeChildAtIndex:function (index, doCleanup) {
310         this.removeChild(this._children[i], doCleanup);
311     },
312 
313     /**
314      * @param {Boolean} [doCleanup=true]
315      */
316     removeAllChildren:function (doCleanup) {
317         var locChildren = this._children;
318         for (var i = 0; i < locChildren.length; i++) {
319             locChildren[i].setBatchNode(null);
320         }
321         cc.Node.prototype.removeAllChildren.call(this, doCleanup);
322         this.textureAtlas.removeAllQuads();
323     },
324 
325     /**
326      * disables a particle by inserting a 0'd quad into the texture atlas
327      * @param {Number} particleIndex
328      */
329     disableParticle:function (particleIndex) {
330         var quad = this.textureAtlas.quads[particleIndex];
331         quad.br.vertices.x = quad.br.vertices.y = quad.tr.vertices.x = quad.tr.vertices.y =
332             quad.tl.vertices.x = quad.tl.vertices.y = quad.bl.vertices.x = quad.bl.vertices.y = 0.0;
333         this.textureAtlas._setDirty(true);
334     },
335 
336     /**
337      * returns the used texture
338      * @return {cc.Texture2D}
339      */
340     getTexture:function () {
341         return this.textureAtlas.texture;
342     },
343 
344     /**
345      * sets a new texture. it will be retained
346      * @param {cc.Texture2D} texture
347      */
348     setTexture:function (texture) {
349         this.textureAtlas.texture = texture;
350 
351         // If the new texture has No premultiplied alpha, AND the blendFunc hasn't been changed, then update it
352         var locBlendFunc = this._blendFunc;
353         if (texture && !texture.hasPremultipliedAlpha() && ( locBlendFunc.src === cc.BLEND_SRC && locBlendFunc.dst === cc.BLEND_DST )) {
354             locBlendFunc.src = cc.SRC_ALPHA;
355             locBlendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
356         }
357     },
358 
359     /**
360      * set the blending function used for the texture
361      * @param {Number|Object} src
362      * @param {Number} dst
363      */
364     setBlendFunc:function (src, dst) {
365         if (dst === undefined){
366             this._blendFunc.src = src.src;
367             this._blendFunc.dst = src.dst;
368         } else{
369             this._blendFunc.src = src;
370             this._blendFunc.src = dst;
371         }
372     },
373 
374     /**
375      * returns the blending function used for the texture
376      * @return {cc.BlendFunc}
377      */
378     getBlendFunc:function () {
379         return new cc.BlendFunc(this._blendFunc.src, this._blendFunc.dst);
380     },
381 
382     _updateAllAtlasIndexes:function () {
383         var index = 0;
384         var locChildren = this._children;
385         for (var i = 0; i < locChildren.length; i++) {
386             var child = locChildren[i];
387             child.setAtlasIndex(index);
388             index += child.getTotalParticles();
389         }
390     },
391 
392     _increaseAtlasCapacityTo:function (quantity) {
393         cc.log("cocos2d: cc.ParticleBatchNode: resizing TextureAtlas capacity from [" + this.textureAtlas.getCapacity()
394             + "] to [" + quantity + "].");
395 
396         if (!this.textureAtlas.resizeCapacity(quantity)) {
397             // serious problems
398             cc.log("cc.ParticleBatchNode._increaseAtlasCapacityTo() : WARNING: Not enough memory to resize the atlas");
399         }
400     },
401 
402     _searchNewPositionInChildrenForZ:function (z) {
403         var locChildren = this._children;
404         var count = locChildren.length;
405         for (var i = 0; i < count; i++) {
406             if (locChildren[i].zIndex > z)
407                 return i;
408         }
409         return count;
410     },
411 
412     _getCurrentIndex:function (child, z) {
413         var foundCurrentIdx = false;
414         var foundNewIdx = false;
415 
416         var newIndex = 0;
417         var oldIndex = 0;
418 
419         var minusOne = 0, locChildren = this._children;
420         var count = locChildren.length;
421         for (var i = 0; i < count; i++) {
422             var pNode = locChildren[i];
423             // new index
424             if (pNode.zIndex > z && !foundNewIdx) {
425                 newIndex = i;
426                 foundNewIdx = true;
427 
428                 if (foundCurrentIdx && foundNewIdx)
429                     break;
430             }
431             // current index
432             if (child === pNode) {
433                 oldIndex = i;
434                 foundCurrentIdx = true;
435                 if (!foundNewIdx)
436                     minusOne = -1;
437                 if (foundCurrentIdx && foundNewIdx)
438                     break;
439             }
440         }
441         if (!foundNewIdx)
442             newIndex = count;
443         newIndex += minusOne;
444         return {newIndex:newIndex, oldIndex:oldIndex};
445     },
446 
447     //
448     // <p>
449     //     don't use lazy sorting, reordering the particle systems quads afterwards would be too complex                                    <br/>
450     //     XXX research whether lazy sorting + freeing current quads and calloc a new block with size of capacity would be faster           <br/>
451     //     XXX or possibly using vertexZ for reordering, that would be fastest                                                              <br/>
452     //     this helper is almost equivalent to CCNode's addChild, but doesn't make use of the lazy sorting                                  <br/>
453     // </p>
454     // @param {cc.ParticleSystem} child
455     // @param {Number} z
456     // @param {Number} aTag
457     // @return {Number}
458     // @private
459     //
460     _addChildHelper:function (child, z, aTag) {
461         if(!child)
462             throw new Error("cc.ParticleBatchNode._addChildHelper(): child should be non-null");
463         if(child.parent){
464             cc.log("cc.ParticleBatchNode._addChildHelper(): child already added. It can't be added again");
465             return null;
466         }
467 
468 
469         if (!this._children)
470             this._children = [];
471 
472         //don't use a lazy insert
473         var pos = this._searchNewPositionInChildrenForZ(z);
474 
475         this._children.splice(pos, 0, child);
476         child.tag = aTag;
477         child._setLocalZOrder(z);
478         child.parent = this;
479         if (this._running) {
480             child.onEnter();
481             child.onEnterTransitionDidFinish();
482         }
483         return pos;
484     },
485 
486     _updateBlendFunc:function () {
487         if (!this.textureAtlas.texture.hasPremultipliedAlpha()) {
488             this._blendFunc.src = cc.SRC_ALPHA;
489             this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
490         }
491     },
492 
493     /**
494      * return the texture atlas used for drawing the quads
495      * @return {cc.TextureAtlas}
496      */
497     getTextureAtlas:function () {
498         return this.textureAtlas;
499     },
500 
501     /**
502      * set the texture atlas used for drawing the quads
503      * @param {cc.TextureAtlas} textureAtlas
504      */
505     setTextureAtlas:function (textureAtlas) {
506         this.textureAtlas = textureAtlas;
507     }
508 });
509 
510 var _p = cc.ParticleBatchNode.prototype;
511 
512 // Extended properties
513 /** @expose */
514 _p.texture;
515 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture);
516 
517 
518 /**
519  * 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
520  * @deprecated since v3.0 please use new cc.ParticleBatchNode(filename, capacity) instead.
521  * @param {String|cc.Texture2D} fileImage
522  * @param {Number} capacity
523  * @return {cc.ParticleBatchNode}
524  */
525 cc.ParticleBatchNode.create = function (fileImage, capacity) {
526     return new cc.ParticleBatchNode(fileImage, capacity);
527 };
528