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