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._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 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