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