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 * 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 of cc.ParticleBatchNode 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