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