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