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