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