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