1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga Inc. 5 6 http://www.cocos2d-x.org 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy 9 of this software and associated documentation files (the "Software"), to deal 10 in the Software without restriction, including without limitation the rights 11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 copies of the Software, and to permit persons to whom the Software is 13 furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in 16 all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 THE SOFTWARE. 25 ****************************************************************************/ 26 27 /** 28 * @constant 29 * @type Number 30 */ 31 cc.DEFAULT_SPRITE_BATCH_CAPACITY = 29; 32 33 /** 34 * <p> 35 * In Canvas render mode ,cc.SpriteBatchNodeCanvas is like a normal node: if it contains children. <br/> 36 * If its _useCache is set to true, it can cache the result that all children of SpriteBatchNode to a canvas <br/> 37 * (often known as "batch draw").<br/> 38 * <br/> 39 * A cc.SpriteBatchNode can reference one and only one texture (one image file, one texture atlas).<br/> 40 * Only the cc.Sprites that are contained in that texture can be added to the cc.SpriteBatchNode.<br/> 41 * All cc.Sprites added to a cc.SpriteBatchNode are drawn in one WebGL draw call. <br/> 42 * If the cc.Sprites are not added to a cc.SpriteBatchNode then an WebGL draw call will be needed for each one, which is less efficient. <br/> 43 * <br/> 44 * Limitations:<br/> 45 * - The only object that is accepted as child (or grandchild, grand-grandchild, etc...) is cc.Sprite or any subclass of cc.Sprite. <br/> 46 * eg: particles, labels and layer can't be added to a cc.SpriteBatchNode. <br/> 47 * - Either all its children are Aliased or Antialiased. It can't be a mix. <br/> 48 * This is because "alias" is a property of the texture, and all the sprites share the same texture. </br> 49 * </p> 50 * @class 51 * @extends cc.Node 52 * @example 53 * //create a SpriteBatchNode 54 * var parent2 = cc.SpriteBatchNode.create("res/animations/grossini.png", 50); 55 */ 56 cc.SpriteBatchNode = cc.Node.extend(/** @lends cc.SpriteBatchNode# */{ 57 _textureAtlas:null, 58 _blendFunc:null, 59 // all descendants: chlidren, gran children, etc... 60 _descendants:null, 61 62 /** 63 * <p> 64 * This is the opposite of "addQuadFromSprite.<br/> 65 * It add the sprite to the children and descendants array, but it doesn't update add it to the texture atlas<br/> 66 * </p> 67 * @param {cc.Sprite} child 68 * @param {Number} z zOrder 69 * @param {Number} aTag 70 * @return {cc.SpriteBatchNode} 71 */ 72 addSpriteWithoutQuad:function (child, z, aTag) { 73 if(!child) 74 throw "cc.SpriteBatchNode.addQuadFromSprite(): child should be non-null"; 75 if(!(child instanceof cc.Sprite)){ 76 cc.log("cc.SpriteBatchNode.addQuadFromSprite(): SpriteBatchNode only supports cc.Sprites as children"); 77 return null; 78 } 79 80 // quad index is Z 81 child.setAtlasIndex(z); 82 83 // XXX: optimize with a binary search 84 var i = 0, locDescendants = this._descendants; 85 if (locDescendants && locDescendants.length > 0) { 86 for (var index = 0; index < locDescendants.length; index++) { 87 var obj = locDescendants[index]; 88 if (obj && (obj.getAtlasIndex() >= z)) 89 ++i; 90 } 91 } 92 this._descendants = cc.ArrayAppendObjectToIndex(locDescendants, child, i); 93 94 // IMPORTANT: Call super, and not self. Avoid adding it to the texture atlas array 95 cc.Node.prototype.addChild.call(this, child, z, aTag); 96 97 //#issue 1262 don't use lazy sorting, tiles are added as quads not as sprites, so sprites need to be added in order 98 this.reorderBatch(false); 99 return this; 100 }, 101 102 // property 103 /** 104 * Return TextureAtlas of cc.SpriteBatchNode 105 * @return {cc.TextureAtlas} 106 */ 107 getTextureAtlas:function () { 108 return this._textureAtlas; 109 }, 110 111 /** 112 * TextureAtlas of cc.SpriteBatchNode setter 113 * @param {cc.TextureAtlas} textureAtlas 114 */ 115 setTextureAtlas:function (textureAtlas) { 116 if (textureAtlas != this._textureAtlas) { 117 this._textureAtlas = textureAtlas; 118 } 119 }, 120 121 /** 122 * Return Descendants of cc.SpriteBatchNode 123 * @return {Array} 124 */ 125 getDescendants:function () { 126 return this._descendants; 127 }, 128 129 /** 130 * <p> 131 * initializes a cc.SpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.<br/> 132 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 133 * The file will be loaded using the TextureMgr. 134 * </p> 135 * @param {String} fileImage 136 * @param {Number} capacity 137 * @return {Boolean} 138 */ 139 initWithFile:function (fileImage, capacity) { 140 var texture2D = cc.TextureCache.getInstance().textureForKey(fileImage); 141 if (!texture2D) 142 texture2D = cc.TextureCache.getInstance().addImage(fileImage); 143 return this.initWithTexture(texture2D, capacity); 144 }, 145 146 _setNodeDirtyForCache:function () { 147 this._cacheDirty = true; 148 }, 149 150 /** 151 * <p> 152 * initializes a cc.SpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.<br/> 153 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 154 * The file will be loaded using the TextureMgr. 155 * </p> 156 * @param {String} fileImage 157 * @param {Number} capacity 158 * @return {Boolean} 159 */ 160 init:function (fileImage, capacity) { 161 var texture2D = cc.TextureCache.getInstance().textureForKey(fileImage); 162 if (!texture2D) 163 texture2D = cc.TextureCache.getInstance().addImage(fileImage); 164 return this.initWithTexture(texture2D, capacity); 165 }, 166 167 /** 168 * increase Atlas Capacity 169 */ 170 increaseAtlasCapacity:function () { 171 // if we're going beyond the current TextureAtlas's capacity, 172 // all the previously initialized sprites will need to redo their texture coords 173 // this is likely computationally expensive 174 var locCapacity = this._textureAtlas.getCapacity(); 175 var quantity = Math.floor((locCapacity + 1) * 4 / 3); 176 177 cc.log("cocos2d: CCSpriteBatchNode: resizing TextureAtlas capacity from " + locCapacity + " to " + quantity + "."); 178 179 if (!this._textureAtlas.resizeCapacity(quantity)) { 180 // serious problems 181 cc.log("cocos2d: WARNING: Not enough memory to resize the atlas"); 182 } 183 }, 184 185 /** 186 * removes a child given a certain index. It will also cleanup the running actions depending on the cleanup parameter. 187 * @warning Removing a child from a cc.SpriteBatchNode is very slow 188 * @param {Number} index 189 * @param {Boolean} doCleanup 190 */ 191 removeChildAtIndex:function (index, doCleanup) { 192 this.removeChild(this._children[index], doCleanup); 193 }, 194 195 /** 196 * rebuild index in order for child 197 * @param {cc.Sprite} pobParent 198 * @param {Number} index 199 * @return {Number} 200 */ 201 rebuildIndexInOrder:function (pobParent, index) { 202 var children = pobParent.getChildren(); 203 if (children && children.length > 0) { 204 for (var i = 0; i < children.length; i++) { 205 var obj = children[i]; 206 if (obj && (obj.getZOrder() < 0)) { 207 index = this.rebuildIndexInOrder(obj, index); 208 } 209 } 210 } 211 // ignore self (batch node) 212 if (!pobParent == this) { 213 pobParent.setAtlasIndex(index); 214 index++; 215 } 216 if (children && children.length > 0) { 217 for (i = 0; i < children.length; i++) { 218 obj = children[i]; 219 if (obj && (obj.getZOrder() >= 0)) { 220 index = this.rebuildIndexInOrder(obj, index); 221 } 222 } 223 } 224 return index; 225 }, 226 227 /** 228 * get highest atlas index in child 229 * @param {cc.Sprite} sprite 230 * @return {Number} 231 */ 232 highestAtlasIndexInChild:function (sprite) { 233 var children = sprite.getChildren(); 234 235 if (!children || children.length == 0) 236 return sprite.getAtlasIndex(); 237 else 238 return this.highestAtlasIndexInChild(children[children.length - 1]); 239 }, 240 241 /** 242 * get lowest atlas index in child 243 * @param {cc.Sprite} sprite 244 * @return {Number} 245 */ 246 lowestAtlasIndexInChild:function (sprite) { 247 var children = sprite.getChildren(); 248 249 if (!children || children.length == 0) 250 return sprite.getAtlasIndex(); 251 else 252 return this.lowestAtlasIndexInChild(children[children.length - 1]); 253 }, 254 255 /** 256 * get atlas index for child 257 * @param {cc.Sprite} sprite 258 * @param {Number} nZ 259 * @return {Number} 260 */ 261 atlasIndexForChild:function (sprite, nZ) { 262 var brothers = sprite.getParent().getChildren(); 263 var childIndex = cc.ArrayGetIndexOfObject(brothers, sprite); 264 265 // ignore parent Z if parent is spriteSheet 266 var ignoreParent = sprite.getParent() == this; 267 var previous = null; 268 if (childIndex > 0 && childIndex < cc.UINT_MAX) 269 previous = brothers[childIndex - 1]; 270 271 // first child of the sprite sheet 272 if (ignoreParent) { 273 if (childIndex == 0) 274 return 0; 275 return this.highestAtlasIndexInChild(previous) + 1; 276 } 277 278 // parent is a cc.Sprite, so, it must be taken into account 279 // first child of an cc.Sprite ? 280 var selParent; 281 if (childIndex == 0) { 282 selParent = sprite.getParent(); 283 284 // less than parent and brothers 285 if (nZ < 0) 286 return selParent.getAtlasIndex(); 287 else 288 return selParent.getAtlasIndex() + 1; 289 } else { 290 // previous & sprite belong to the same branch 291 if ((previous.getZOrder() < 0 && nZ < 0) || (previous.getZOrder() >= 0 && nZ >= 0)) 292 return this.highestAtlasIndexInChild(previous) + 1; 293 294 // else (previous < 0 and sprite >= 0 ) 295 selParent = sprite.getParent(); 296 return selParent.getAtlasIndex() + 1; 297 } 298 }, 299 300 /** 301 * Sprites use this to start sortChildren, don't call this manually 302 * @param {Boolean} reorder 303 */ 304 reorderBatch:function (reorder) { 305 this._reorderChildDirty = reorder; 306 }, 307 308 /** 309 * set the source blending function for the texture 310 * @param {Number | cc.BlendFunc} src 311 * @param {Number} dst 312 */ 313 setBlendFunc:function (src, dst) { 314 if (arguments.length == 1) 315 this._blendFunc = src; 316 else 317 this._blendFunc = {src:src, dst:dst}; 318 }, 319 320 /** 321 * returns the blending function used for the texture 322 * @return {cc.BlendFunc} 323 */ 324 getBlendFunc:function () { 325 return this._blendFunc; 326 }, 327 328 /** 329 * (override reorderChild of cc.Node) 330 * @override 331 * @param {cc.Sprite} child 332 * @param {Number} zOrder 333 */ 334 reorderChild:function (child, zOrder) { 335 if(!child) 336 throw "cc.SpriteBatchNode.addChild():child should be non-null"; 337 if(this._children.indexOf(child) === -1) { 338 cc.log("cc.SpriteBatchNode.addChild(): Child doesn't belong to Sprite"); 339 return; 340 } 341 342 if (zOrder === child.getZOrder()) 343 return; 344 345 //set the z-order and sort later 346 cc.Node.prototype.reorderChild.call(this, child, zOrder); 347 this.setNodeDirty(); 348 }, 349 350 /** 351 * remove child from cc.SpriteBatchNode (override removeChild of cc.Node) 352 * @param {cc.Sprite} child 353 * @param cleanup 354 */ 355 removeChild:function (child, cleanup) { 356 // explicit null handling 357 if (child == null) 358 return; 359 if(this._children.indexOf(child) === -1){ 360 cc.log("cc.SpriteBatchNode.addChild(): sprite batch node should contain the child"); 361 return; 362 } 363 364 // cleanup before removing 365 this.removeSpriteFromAtlas(child); 366 cc.Node.prototype.removeChild.call(this, child, cleanup); 367 }, 368 369 _mvpMatrix:null, 370 _textureForCanvas:null, 371 _useCache:false, 372 _originalTexture:null, 373 374 /** 375 * Constructor 376 * @param {String} fileImage 377 */ 378 ctor: null, 379 380 _ctorForCanvas: function (fileImage) { 381 cc.Node.prototype.ctor.call(this); 382 if (fileImage) 383 this.init(fileImage, cc.DEFAULT_SPRITE_BATCH_CAPACITY); 384 }, 385 386 _ctorForWebGL: function (fileImage) { 387 cc.Node.prototype.ctor.call(this); 388 this._mvpMatrix = new cc.kmMat4(); 389 if (fileImage) 390 this.init(fileImage, cc.DEFAULT_SPRITE_BATCH_CAPACITY); 391 }, 392 393 394 /** 395 * <p> 396 * Updates a quad at a certain index into the texture atlas. The CCSprite won't be added into the children array. <br/> 397 * This method should be called only when you are dealing with very big AtlasSrite and when most of the cc.Sprite won't be updated.<br/> 398 * For example: a tile map (cc.TMXMap) or a label with lots of characters (BitmapFontAtlas)<br/> 399 * </p> 400 * @param {cc.Sprite} sprite 401 * @param {Number} index 402 */ 403 updateQuadFromSprite:null, 404 405 _updateQuadFromSpriteForCanvas:function (sprite, index) { 406 if(!sprite) 407 throw "cc.SpriteBatchNode.updateQuadFromSprite(): sprite should be non-null"; 408 if(!(sprite instanceof cc.Sprite)){ 409 cc.log("cc.SpriteBatchNode.updateQuadFromSprite(): cc.SpriteBatchNode only supports cc.Sprites as children"); 410 return; 411 } 412 413 // 414 // update the quad directly. Don't add the sprite to the scene graph 415 // 416 sprite.setBatchNode(this); 417 sprite.setAtlasIndex(index); 418 419 sprite.setDirty(true); 420 // UpdateTransform updates the textureAtlas quad 421 sprite.updateTransform(); 422 }, 423 424 _updateQuadFromSpriteForWebGL:function (sprite, index) { 425 if(!sprite) 426 throw "cc.SpriteBatchNode.updateQuadFromSprite(): sprite should be non-null"; 427 if(!(sprite instanceof cc.Sprite)){ 428 cc.log("cc.SpriteBatchNode.updateQuadFromSprite(): cc.SpriteBatchNode only supports cc.Sprites as children"); 429 return; 430 } 431 432 // make needed room 433 var locCapacity = this._textureAtlas.getCapacity(); 434 while (index >= locCapacity || locCapacity == this._textureAtlas.getTotalQuads()) { 435 this.increaseAtlasCapacity(); 436 } 437 438 // 439 // update the quad directly. Don't add the sprite to the scene graph 440 // 441 sprite.setBatchNode(this); 442 sprite.setAtlasIndex(index); 443 444 sprite.setDirty(true); 445 // UpdateTransform updates the textureAtlas quad 446 sprite.updateTransform(); 447 }, 448 449 _swap:function (oldIndex, newIndex) { 450 var locDescendants = this._descendants; 451 var locTextureAtlas = this._textureAtlas; 452 var quads = locTextureAtlas.getQuads(); 453 var tempItem = locDescendants[oldIndex]; 454 var tempIteQuad = cc.V3F_C4B_T2F_QuadCopy(quads[oldIndex]); 455 456 //update the index of other swapped item 457 locDescendants[newIndex].setAtlasIndex(oldIndex); 458 locDescendants[oldIndex] = locDescendants[newIndex]; 459 460 locTextureAtlas.updateQuad(quads[newIndex], oldIndex); 461 locDescendants[newIndex] = tempItem; 462 locTextureAtlas.updateQuad(tempIteQuad, newIndex); 463 }, 464 465 /** 466 * <p> 467 * Inserts a quad at a certain index into the texture atlas. The cc.Sprite won't be added into the children array. <br/> 468 * This method should be called only when you are dealing with very big AtlasSprite and when most of the cc.Sprite won't be updated. <br/> 469 * For example: a tile map (cc.TMXMap) or a label with lots of characters (cc.LabelBMFont) 470 * </p> 471 * @param {cc.Sprite} sprite 472 * @param {Number} index 473 */ 474 insertQuadFromSprite:null, 475 476 _insertQuadFromSpriteForCanvas:function (sprite, index) { 477 if(!sprite) 478 throw "cc.SpriteBatchNode.insertQuadFromSprite(): sprite should be non-null"; 479 if(!(sprite instanceof cc.Sprite)){ 480 cc.log("cc.SpriteBatchNode.insertQuadFromSprite(): cc.SpriteBatchNode only supports cc.Sprites as children"); 481 return; 482 } 483 484 // 485 // update the quad directly. Don't add the sprite to the scene graph 486 // 487 sprite.setBatchNode(this); 488 sprite.setAtlasIndex(index); 489 490 // XXX: updateTransform will update the textureAtlas too, using updateQuad. 491 // XXX: so, it should be AFTER the insertQuad 492 sprite.setDirty(true); 493 sprite.updateTransform(); 494 this._children = cc.ArrayAppendObjectToIndex(this._children, sprite, index); 495 }, 496 497 _insertQuadFromSpriteForWebGL:function (sprite, index) { 498 if(!sprite) 499 throw "cc.SpriteBatchNode.insertQuadFromSprite(): sprite should be non-null"; 500 if(!(sprite instanceof cc.Sprite)){ 501 cc.log("cc.SpriteBatchNode.insertQuadFromSprite(): cc.SpriteBatchNode only supports cc.Sprites as children"); 502 return; 503 } 504 505 // make needed room 506 var locTextureAtlas = this._textureAtlas; 507 while (index >= locTextureAtlas.getCapacity() || locTextureAtlas.getCapacity() === locTextureAtlas.getTotalQuads()) 508 this.increaseAtlasCapacity(); 509 510 // 511 // update the quad directly. Don't add the sprite to the scene graph 512 // 513 sprite.setBatchNode(this); 514 sprite.setAtlasIndex(index); 515 locTextureAtlas.insertQuad(sprite.getQuad(), index); 516 517 // XXX: updateTransform will update the textureAtlas too, using updateQuad. 518 // XXX: so, it should be AFTER the insertQuad 519 sprite.setDirty(true); 520 sprite.updateTransform(); 521 }, 522 523 _updateAtlasIndex:function (sprite, curIndex) { 524 var count = 0; 525 var pArray = sprite.getChildren(); 526 if (pArray) 527 count = pArray.length; 528 529 var oldIndex = 0; 530 if (count === 0) { 531 oldIndex = sprite.getAtlasIndex(); 532 sprite.setAtlasIndex(curIndex); 533 sprite.setOrderOfArrival(0); 534 if (oldIndex != curIndex) 535 this._swap(oldIndex, curIndex); 536 curIndex++; 537 } else { 538 var needNewIndex = true; 539 if (pArray[0].getZOrder() >= 0) { 540 //all children are in front of the parent 541 oldIndex = sprite.getAtlasIndex(); 542 sprite.setAtlasIndex(curIndex); 543 sprite.setOrderOfArrival(0); 544 if (oldIndex != curIndex) 545 this._swap(oldIndex, curIndex); 546 curIndex++; 547 needNewIndex = false; 548 } 549 for (var i = 0; i < pArray.length; i++) { 550 var child = pArray[i]; 551 if (needNewIndex && child.getZOrder() >= 0) { 552 oldIndex = sprite.getAtlasIndex(); 553 sprite.setAtlasIndex(curIndex); 554 sprite.setOrderOfArrival(0); 555 if (oldIndex != curIndex) { 556 this._swap(oldIndex, curIndex); 557 } 558 curIndex++; 559 needNewIndex = false; 560 } 561 curIndex = this._updateAtlasIndex(child, curIndex); 562 } 563 564 if (needNewIndex) { 565 //all children have a zOrder < 0) 566 oldIndex = sprite.getAtlasIndex(); 567 sprite.setAtlasIndex(curIndex); 568 sprite.setOrderOfArrival(0); 569 if (oldIndex != curIndex) { 570 this._swap(oldIndex, curIndex); 571 } 572 curIndex++; 573 } 574 } 575 576 return curIndex; 577 }, 578 579 _updateBlendFunc:function () { 580 if (!this._textureAtlas.getTexture().hasPremultipliedAlpha()) { 581 this._blendFunc.src = gl.SRC_ALPHA; 582 this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA; 583 } 584 }, 585 586 /** 587 * <p> 588 * initializes a CCSpriteBatchNode with a texture2d and capacity of children.<br/> 589 * The capacity will be increased in 33% in runtime if it run out of space. 590 * </p> 591 * @param {cc.Texture2D} tex 592 * @param {Number} [capacity] 593 * @return {Boolean} 594 */ 595 initWithTexture:null, 596 597 _initWithTextureForCanvas:function (tex, capacity) { 598 this._children = []; 599 this._descendants = []; 600 601 this._blendFunc = new cc.BlendFunc(cc.BLEND_SRC, cc.BLEND_DST); 602 603 this._originalTexture = tex; 604 this._textureForCanvas = tex; 605 return true; 606 }, 607 608 _initWithTextureForWebGL:function (tex, capacity) { 609 this._children = []; 610 this._descendants = []; 611 612 this._blendFunc = new cc.BlendFunc(cc.BLEND_SRC, cc.BLEND_DST); 613 capacity = capacity || cc.DEFAULT_SPRITE_BATCH_CAPACITY; 614 this._textureAtlas = new cc.TextureAtlas(); 615 this._textureAtlas.initWithTexture(tex, capacity); 616 this._updateBlendFunc(); 617 this.setShaderProgram(cc.ShaderCache.getInstance().programForKey(cc.SHADER_POSITION_TEXTURECOLOR)); 618 return true; 619 }, 620 621 /** 622 * add child helper 623 * @param {cc.Sprite} sprite 624 * @param {Number} index 625 */ 626 insertChild:function (sprite, index) { 627 sprite.setBatchNode(this); 628 sprite.setAtlasIndex(index); 629 sprite.setDirty(true); 630 631 var locTextureAtlas = this._textureAtlas; 632 if (locTextureAtlas.getTotalQuads() >= locTextureAtlas.getCapacity()) 633 this.increaseAtlasCapacity(); 634 635 locTextureAtlas.insertQuad(sprite.getQuad(), index); 636 this._descendants = cc.ArrayAppendObjectToIndex(this._descendants, sprite, index); 637 638 // update indices 639 var i = index + 1, locDescendant = this._descendants; 640 if (locDescendant && locDescendant.length > 0) { 641 for (; i < locDescendant.length; i++) 642 locDescendant[i].setAtlasIndex(locDescendant[i].getAtlasIndex() + 1); 643 } 644 645 // add children recursively 646 var locChildren = sprite.getChildren(); 647 if (locChildren && locChildren.length > 0) { 648 for (i = 0; i < locChildren.length; i++) { 649 if (locChildren[i]) { 650 var getIndex = this.atlasIndexForChild(locChildren[i], locChildren[i].getZOrder()); 651 this.insertChild(locChildren[i], getIndex); 652 } 653 } 654 } 655 }, 656 657 /** 658 * addChild helper, faster than insertChild 659 * @param {cc.Sprite} sprite 660 */ 661 appendChild:null, 662 663 _appendChildForCanvas:function (sprite) { 664 this._reorderChildDirty = true; 665 sprite.setBatchNode(this); 666 sprite.setDirty(true); 667 668 this._descendants.push(sprite); 669 var index = this._descendants.length - 1; 670 sprite.setAtlasIndex(index); 671 672 // add children recursively 673 var children = sprite.getChildren(); 674 for (var i = 0; i < children.length; i++) 675 this.appendChild(children[i]); 676 }, 677 678 _appendChildForWebGL:function (sprite) { 679 this._reorderChildDirty = true; 680 sprite.setBatchNode(this); 681 sprite.setDirty(true); 682 683 this._descendants.push(sprite); 684 var index = this._descendants.length - 1; 685 sprite.setAtlasIndex(index); 686 687 var locTextureAtlas = this._textureAtlas; 688 if (locTextureAtlas.getTotalQuads() == locTextureAtlas.getCapacity()) 689 this.increaseAtlasCapacity(); 690 locTextureAtlas.insertQuad(sprite.getQuad(), index); 691 692 // add children recursively 693 var children = sprite.getChildren(); 694 for (var i = 0; i < children.length; i++) 695 this.appendChild(children[i]); 696 }, 697 698 /** 699 * remove sprite from TextureAtlas 700 * @param {cc.Sprite} sprite 701 */ 702 removeSpriteFromAtlas:null, 703 704 _removeSpriteFromAtlasForCanvas:function (sprite) { 705 // Cleanup sprite. It might be reused (issue #569) 706 sprite.setBatchNode(null); 707 var locDescendants = this._descendants; 708 var index = cc.ArrayGetIndexOfObject(locDescendants, sprite); 709 if (index != -1) { 710 cc.ArrayRemoveObjectAtIndex(locDescendants, index); 711 712 // update all sprites beyond this one 713 var len = locDescendants.length; 714 for (; index < len; ++index) { 715 var s = locDescendants[index]; 716 s.setAtlasIndex(s.getAtlasIndex() - 1); 717 } 718 } 719 720 // remove children recursively 721 var children = sprite.getChildren(); 722 if (children && children.length > 0) { 723 for (var i = 0; i < children.length; i++) 724 if (children[i]) 725 this.removeSpriteFromAtlas(children[i]); 726 } 727 }, 728 729 _removeSpriteFromAtlasForWebGL:function (sprite) { 730 this._textureAtlas.removeQuadAtIndex(sprite.getAtlasIndex()); // remove from TextureAtlas 731 732 // Cleanup sprite. It might be reused (issue #569) 733 sprite.setBatchNode(null); 734 735 var locDescendants = this._descendants; 736 var index = cc.ArrayGetIndexOfObject(locDescendants, sprite); 737 if (index != -1) { 738 cc.ArrayRemoveObjectAtIndex(locDescendants, index); 739 740 // update all sprites beyond this one 741 742 var len = locDescendants.length; 743 for (; index < len; ++index) { 744 var s = locDescendants[index]; 745 s.setAtlasIndex(s.getAtlasIndex() - 1); 746 } 747 } 748 749 // remove children recursively 750 var children = sprite.getChildren(); 751 if (children && children.length > 0) { 752 for (var i = 0; i < children.length; i++) 753 if (children[i]) 754 this.removeSpriteFromAtlas(children[i]); 755 } 756 }, 757 // CCTextureProtocol 758 /** 759 * Return texture of cc.SpriteBatchNode 760 * @return {cc.Texture2D|HTMLImageElement|HTMLCanvasElement} 761 */ 762 getTexture:null, 763 764 _getTextureForCanvas:function () { 765 return this._textureForCanvas; 766 }, 767 768 _getTextureForWebGL:function () { 769 return this._textureAtlas.getTexture(); 770 }, 771 772 /** 773 * texture of cc.SpriteBatchNode setter 774 * @param {cc.Texture2D} texture 775 */ 776 setTexture:null, 777 778 _setTextureForCanvas:function (texture) { 779 this._textureForCanvas = texture; 780 var locChildren = this._children; 781 for (var i = 0; i < locChildren.length; i++) 782 locChildren[i].setTexture(texture); 783 }, 784 785 _setTextureForWebGL:function (texture) { 786 this._textureAtlas.setTexture(texture); 787 this._updateBlendFunc(); 788 }, 789 790 /** 791 * don't call visit on it's children ( override visit of cc.Node ) 792 * @override 793 * @param {CanvasRenderingContext2D} ctx 794 */ 795 visit:null, 796 797 _visitForCanvas:function (ctx) { 798 var context = ctx || cc.renderContext; 799 // quick return if not visible 800 if (!this._visible) 801 return; 802 803 context.save(); 804 this.transform(ctx); 805 var i, locChildren = this._children; 806 807 if (locChildren) { 808 this.sortAllChildren(); 809 for (i = 0; i < locChildren.length; i++) { 810 if (locChildren[i]) 811 locChildren[i].visit(context); 812 } 813 } 814 815 context.restore(); 816 }, 817 818 _visitForWebGL:function (ctx) { 819 var gl = ctx || cc.renderContext; 820 821 // CAREFUL: 822 // This visit is almost identical to CocosNode#visit 823 // with the exception that it doesn't call visit on it's children 824 // 825 // The alternative is to have a void CCSprite#visit, but 826 // although this is less mantainable, is faster 827 // 828 if (!this._visible) 829 return; 830 cc.kmGLPushMatrix(); 831 var locGrid = this._grid; 832 if (locGrid && locGrid.isActive()) { 833 locGrid.beforeDraw(); 834 this.transformAncestors(); 835 } 836 this.sortAllChildren(); 837 this.transform(gl); 838 this.draw(gl); 839 if (locGrid && locGrid.isActive()) 840 locGrid.afterDraw(this); 841 cc.kmGLPopMatrix(); 842 this.setOrderOfArrival(0); 843 }, 844 845 /** 846 * add child to cc.SpriteBatchNode (override addChild of cc.Node) 847 * @override 848 * @param {cc.Sprite} child 849 * @param {Number} [zOrder] 850 * @param {Number} [tag] 851 */ 852 addChild: null, 853 854 _addChildForCanvas: function (child, zOrder, tag) { 855 if (child == null) 856 throw "cc.SpriteBatchNode.addChild(): child should be non-null"; 857 if(!(child instanceof cc.Sprite)){ 858 cc.log( "cc.SpriteBatchNode.addChild(): cc.SpriteBatchNode only supports cc.Sprites as children"); 859 return; 860 } 861 862 zOrder = (zOrder == null) ? child.getZOrder() : zOrder; 863 tag = (tag == null) ? child.getTag() : tag; 864 865 cc.Node.prototype.addChild.call(this, child, zOrder, tag); 866 this.appendChild(child); 867 this.setNodeDirty(); 868 }, 869 870 _addChildForWebGL: function (child, zOrder, tag) { 871 if (child == null) 872 throw "cc.SpriteBatchNode.addChild(): child should be non-null"; 873 if(!(child instanceof cc.Sprite)){ 874 cc.log( "cc.SpriteBatchNode.addChild(): cc.SpriteBatchNode only supports cc.Sprites as children"); 875 return; 876 } 877 if(child.getTexture() != this._textureAtlas.getTexture()){ // check cc.Sprite is using the same texture id 878 cc.log( "cc.SpriteBatchNode.addChild(): cc.Sprite is not using the same texture"); 879 return; 880 } 881 882 zOrder = (zOrder == null) ? child.getZOrder() : zOrder; 883 tag = (tag == null) ? child.getTag() : tag; 884 885 cc.Node.prototype.addChild.call(this, child, zOrder, tag); 886 this.appendChild(child); 887 this.setNodeDirty(); 888 }, 889 890 /** 891 * <p>Removes all children from the container and do a cleanup all running actions depending on the cleanup parameter. <br/> 892 * (override removeAllChildren of cc.Node)</p> 893 * @param {Boolean} cleanup 894 */ 895 removeAllChildren:null, 896 897 _removeAllChildrenForCanvas:function (cleanup) { 898 // Invalidate atlas index. issue #569 899 // useSelfRender should be performed on all descendants. issue #1216 900 var locDescendants = this._descendants; 901 if (locDescendants && locDescendants.length > 0) { 902 for (var i = 0, len = locDescendants.length; i < len; i++) { 903 if (locDescendants[i]) 904 locDescendants[i].setBatchNode(null); 905 } 906 } 907 908 cc.Node.prototype.removeAllChildren.call(this, cleanup); 909 this._descendants = []; 910 }, 911 912 _removeAllChildrenForWebGL:function (cleanup) { 913 // Invalidate atlas index. issue #569 914 // useSelfRender should be performed on all descendants. issue #1216 915 var locDescendants = this._descendants; 916 if (locDescendants && locDescendants.length > 0) { 917 for (var i = 0, len = locDescendants.length; i < len; i++) { 918 if (locDescendants[i]) 919 locDescendants[i].setBatchNode(null); 920 } 921 } 922 cc.Node.prototype.removeAllChildren.call(this, cleanup); 923 this._descendants = []; 924 this._textureAtlas.removeAllQuads(); 925 }, 926 927 sortAllChildren:null, 928 929 _sortAllChildrenForCanvas:function () { 930 if (this._reorderChildDirty) { 931 var i, j = 0, locChildren = this._children; 932 var length = locChildren.length, tempChild; 933 //insertion sort 934 for (i = 1; i < length; i++) { 935 var tempItem = locChildren[i]; 936 j = i - 1; 937 tempChild = locChildren[j]; 938 939 //continue moving element downwards while zOrder is smaller or when zOrder is the same but mutatedIndex is smaller 940 while (j >= 0 && ( tempItem._zOrder < tempChild._zOrder || 941 ( tempItem._zOrder == tempChild._zOrder && tempItem._orderOfArrival < tempChild._orderOfArrival ))) { 942 locChildren[j + 1] = tempChild; 943 j = j - 1; 944 tempChild = locChildren[j]; 945 } 946 locChildren[j + 1] = tempItem; 947 } 948 949 //sorted now check all children 950 if (locChildren.length > 0) { 951 //first sort all children recursively based on zOrder 952 this._arrayMakeObjectsPerformSelector(locChildren, cc.Node.StateCallbackType.sortAllChildren); 953 } 954 this._reorderChildDirty = false; 955 } 956 }, 957 958 _sortAllChildrenForWebGL:function () { 959 if (this._reorderChildDirty) { 960 var childrenArr = this._children; 961 var i, j = 0, length = childrenArr.length, tempChild; 962 //insertion sort 963 for (i = 1; i < length; i++) { 964 var tempItem = childrenArr[i]; 965 j = i - 1; 966 tempChild = childrenArr[j]; 967 968 //continue moving element downwards while zOrder is smaller or when zOrder is the same but mutatedIndex is smaller 969 while (j >= 0 && ( tempItem._zOrder < tempChild._zOrder || 970 ( tempItem._zOrder == tempChild._zOrder && tempItem._orderOfArrival < tempChild._orderOfArrival ))) { 971 childrenArr[j + 1] = tempChild; 972 j = j - 1; 973 tempChild = childrenArr[j]; 974 } 975 childrenArr[j + 1] = tempItem; 976 } 977 978 //sorted now check all children 979 if (childrenArr.length > 0) { 980 //first sort all children recursively based on zOrder 981 this._arrayMakeObjectsPerformSelector(childrenArr, cc.Node.StateCallbackType.sortAllChildren); 982 983 var index = 0; 984 //fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact) 985 // and at the same time reorder descedants and the quads to the right index 986 for (i = 0; i < childrenArr.length; i++) 987 index = this._updateAtlasIndex(childrenArr[i], index); 988 } 989 this._reorderChildDirty = false; 990 } 991 }, 992 /** 993 * draw cc.SpriteBatchNode (override draw of cc.Node) 994 */ 995 draw:null, 996 997 _drawForWebGL:function () { 998 // Optimization: Fast Dispatch 999 if (this._textureAtlas.getTotalQuads() === 0) 1000 return; 1001 1002 //cc.NODE_DRAW_SETUP(this); 1003 this._shaderProgram.use(); 1004 this._shaderProgram.setUniformForModelViewAndProjectionMatrixWithMat4(); 1005 this._arrayMakeObjectsPerformSelector(this._children, cc.Node.StateCallbackType.updateTransform); 1006 cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst); 1007 1008 this._textureAtlas.drawQuads(); 1009 } 1010 }); 1011 1012 if(cc.Browser.supportWebGL){ 1013 cc.SpriteBatchNode.prototype.ctor = cc.SpriteBatchNode.prototype._ctorForWebGL; 1014 cc.SpriteBatchNode.prototype.updateQuadFromSprite = cc.SpriteBatchNode.prototype._updateQuadFromSpriteForWebGL; 1015 cc.SpriteBatchNode.prototype.insertQuadFromSprite = cc.SpriteBatchNode.prototype._insertQuadFromSpriteForWebGL; 1016 cc.SpriteBatchNode.prototype.initWithTexture = cc.SpriteBatchNode.prototype._initWithTextureForWebGL; 1017 cc.SpriteBatchNode.prototype.appendChild = cc.SpriteBatchNode.prototype._appendChildForWebGL; 1018 cc.SpriteBatchNode.prototype.removeSpriteFromAtlas = cc.SpriteBatchNode.prototype._removeSpriteFromAtlasForWebGL; 1019 cc.SpriteBatchNode.prototype.getTexture = cc.SpriteBatchNode.prototype._getTextureForWebGL; 1020 cc.SpriteBatchNode.prototype.setTexture = cc.SpriteBatchNode.prototype._setTextureForWebGL; 1021 cc.SpriteBatchNode.prototype.visit = cc.SpriteBatchNode.prototype._visitForWebGL; 1022 cc.SpriteBatchNode.prototype.addChild = cc.SpriteBatchNode.prototype._addChildForWebGL; 1023 cc.SpriteBatchNode.prototype.removeAllChildren = cc.SpriteBatchNode.prototype._removeAllChildrenForWebGL; 1024 cc.SpriteBatchNode.prototype.sortAllChildren = cc.SpriteBatchNode.prototype._sortAllChildrenForWebGL; 1025 cc.SpriteBatchNode.prototype.draw = cc.SpriteBatchNode.prototype._drawForWebGL; 1026 } else { 1027 cc.SpriteBatchNode.prototype.ctor = cc.SpriteBatchNode.prototype._ctorForCanvas; 1028 cc.SpriteBatchNode.prototype.updateQuadFromSprite = cc.SpriteBatchNode.prototype._updateQuadFromSpriteForCanvas; 1029 cc.SpriteBatchNode.prototype.insertQuadFromSprite = cc.SpriteBatchNode.prototype._insertQuadFromSpriteForCanvas; 1030 cc.SpriteBatchNode.prototype.initWithTexture = cc.SpriteBatchNode.prototype._initWithTextureForCanvas; 1031 cc.SpriteBatchNode.prototype.appendChild = cc.SpriteBatchNode.prototype._appendChildForCanvas; 1032 cc.SpriteBatchNode.prototype.removeSpriteFromAtlas = cc.SpriteBatchNode.prototype._removeSpriteFromAtlasForCanvas; 1033 cc.SpriteBatchNode.prototype.getTexture = cc.SpriteBatchNode.prototype._getTextureForCanvas; 1034 cc.SpriteBatchNode.prototype.setTexture = cc.SpriteBatchNode.prototype._setTextureForCanvas; 1035 cc.SpriteBatchNode.prototype.visit = cc.SpriteBatchNode.prototype._visitForCanvas; 1036 cc.SpriteBatchNode.prototype.removeAllChildren = cc.SpriteBatchNode.prototype._removeAllChildrenForCanvas; 1037 cc.SpriteBatchNode.prototype.addChild = cc.SpriteBatchNode.prototype._addChildForCanvas; 1038 cc.SpriteBatchNode.prototype.sortAllChildren = cc.SpriteBatchNode.prototype._sortAllChildrenForCanvas; 1039 cc.SpriteBatchNode.prototype.draw = cc.Node.prototype.draw; 1040 } 1041 1042 /** 1043 * <p> 1044 * creates a cc.SpriteBatchNodeCanvas with a file image (.png, .jpg etc) with a default capacity of 29 children.<br/> 1045 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 1046 * The file will be loaded using the TextureMgr.<br/> 1047 * </p> 1048 * @param {String} fileImage 1049 * @param {Number} capacity 1050 * @return {cc.SpriteBatchNode} 1051 * @example 1052 * //create a SpriteBatchNode 1053 * var parent2 = cc.SpriteBatchNode.create("res/animations/grossini.png", 50); 1054 */ 1055 cc.SpriteBatchNode.create = function (fileImage, capacity) { 1056 capacity = capacity || cc.DEFAULT_SPRITE_BATCH_CAPACITY; 1057 var batchNode = new cc.SpriteBatchNode(); 1058 batchNode.init(fileImage, capacity); 1059 return batchNode; 1060 }; 1061 1062 /** 1063 * <p> 1064 * creates a cc.SpriteBatchNodeCanvas with a texture2d and a default capacity of 29 children. <br/> 1065 * The capacity will be increased in 33% in runtime if it run out of space. <br/> 1066 * </p> 1067 * @param {cc.Texture2D} texture 1068 * @param {Number} capacity 1069 * @return {cc.SpriteBatchNode} 1070 */ 1071 cc.SpriteBatchNode.createWithTexture = function (texture, capacity) { 1072 capacity = capacity || cc.DEFAULT_SPRITE_BATCH_CAPACITY; 1073 var batchNode = new cc.SpriteBatchNode(); 1074 batchNode.initWithTexture(texture, capacity); 1075 return batchNode; 1076 };