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