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