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 
 29 /**
 30  * @constant
 31  * @type Number
 32  */
 33 cc.DEFAULT_SPRITE_BATCH_CAPACITY = 29;
 34 
 35 /**
 36  * <p>
 37  *     In WebGL render mode ,cc.SpriteBatchNode is like a batch node: if it contains children, it will draw them in 1 single OpenGL call<br/>
 38  *     (often known as "batch draw").<br/>
 39  *     <br/>
 40  *     A cc.SpriteBatchNode can reference one and only one texture (one image file, one texture atlas).<br/>
 41  *     Only the cc.Sprites that are contained in that texture can be added to the cc.SpriteBatchNode.<br/>
 42  *     All cc.Sprites added to a cc.SpriteBatchNode are drawn in one OpenGL ES draw call. <br/>
 43  *     If the CCSprites are not added to a CCSpriteBatchNode then an OpenGL ES draw call will be needed for each one, which is less efficient. <br/>
 44  *     <br/>
 45  *     Limitations:<br/>
 46  *       - The only object that is accepted as child (or grandchild, grand-grandchild, etc...) is CCSprite or any subclass of CCSprite. <br/>
 47  *          eg: particles, labels and layer can't be added to a CCSpriteBatchNode. <br/>
 48  *       - Either all its children are Aliased or Antialiased. It can't be a mix. <br/>
 49  *          This is because "alias" is a property of the texture, and all the sprites share the same texture. </br>
 50  * </p>
 51  * @class
 52  * @extends cc.Node
 53  * @example
 54  * //create a SpriteBatchNode
 55  * var parent2 = cc.SpriteBatchNode.create("res/animations/grossini.png", 50);
 56  */
 57 cc.SpriteBatchNode = cc.Node.extend(/** @lends cc.SpriteBatchNode# */{
 58     _textureAtlas:new cc.TextureAtlas(),
 59     _blendFunc:new cc.BlendFunc(0, 0),
 60     // all descendants: chlidren, gran children, etc...
 61     _descendants:[],
 62     _renderTexture:null,
 63     _isUseCache:false,
 64     _originalTexture:null,
 65     /**
 66      * Constructor
 67      * @param {String} fileImage
 68      */
 69     ctor:function (fileImage) {
 70         this._super();
 71         if (fileImage) {
 72             this.init(fileImage, cc.DEFAULT_SPRITE_BATCH_CAPACITY);
 73         }
 74         this._renderTexture = cc.RenderTexture.create(cc.canvas.width, cc.canvas.height);
 75         this.setContentSize(cc.size(cc.canvas.width, cc.canvas.height));
 76     },
 77     setContentSize:function (size) {
 78         if (!size) {
 79             return;
 80         }
 81 
 82         this._super(size);
 83         this._renderTexture.setContentSize(size);
 84     },
 85     _updateBlendFunc:function () {
 86         if (!this._textureAtlas.getTexture().hasPremultipliedAlpha()) {
 87             this._blendFunc.src = gl.SRC_ALPHA;
 88             this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA;
 89         }
 90     },
 91 
 92     _updateAtlasIndex:function (sprite, curIndex) {
 93         var count = 0;
 94         var pArray = sprite.getChildren();
 95         if (pArray) {
 96             count = pArray.length;
 97         }
 98 
 99         var oldIndex = 0;
100         if (count == 0) {
101             oldIndex = sprite.getAtlasIndex();
102             sprite.setAtlasIndex(curIndex);
103             sprite.setOrderOfArrival(0);
104             if (oldIndex != curIndex) {
105                 this._swap(oldIndex, curIndex);
106             }
107             curIndex++;
108         } else {
109             var needNewIndex = true;
110             if (pArray[0].getZOrder() >= 0) {
111                 //all children are in front of the parent
112                 oldIndex = sprite.getAtlasIndex();
113                 sprite.setAtlasIndex(curIndex);
114                 sprite.setOrderOfArrival(0);
115                 if (oldIndex != curIndex) {
116                     this._swap(oldIndex, curIndex);
117                 }
118                 curIndex++;
119                 needNewIndex = false;
120             }
121 
122             for (var i = 0; i < pArray.length; i++) {
123                 var child = pArray[i];
124                 if (needNewIndex && child.getZOrder() >= 0) {
125                     oldIndex = sprite.getAtlasIndex();
126                     sprite.setAtlasIndex(curIndex);
127                     sprite.setOrderOfArrival(0);
128                     if (oldIndex != curIndex) {
129                         this._swap(oldIndex, curIndex);
130                     }
131                     curIndex++;
132                     needNewIndex = false;
133                 }
134                 this._updateAtlasIndex(child, curIndex);
135             }
136 
137             if (needNewIndex) {
138                 //all children have a zOrder < 0)
139                 oldIndex = sprite.getAtlasIndex();
140                 sprite.setAtlasIndex(curIndex);
141                 sprite.setOrderOfArrival(0);
142                 if (oldIndex != curIndex) {
143                     this._swap(oldIndex, curIndex);
144                 }
145                 curIndex++;
146             }
147         }
148 
149         return curIndex;
150     },
151 
152     _swap:function (oldIndex, newIndex) {
153         var quads = this._textureAtlas.getQuads();
154         var tempItem = this._descendants[oldIndex];
155         var tempIteQuad = quads[oldIndex];
156 
157         //update the index of other swapped item
158         this._descendants[newIndex].setAtlasIndex(oldIndex);
159 
160         this._descendants[oldIndex] = this._descendants[newIndex];
161         quads[oldIndex] = quads[newIndex];
162         this._descendants[newIndex] = tempItem;
163         quads[newIndex] = tempIteQuad;
164     },
165 
166     // IMPORTANT XXX IMPORTNAT:
167     // These 2 methods can't be part of cc.TMXLayer since they call [super add...], and cc.SpriteSheet#add SHALL not be called
168 
169     /**
170      * <p>
171      *   Adds a quad into the texture atlas but it won't be added into the children array.<br/>
172      *   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/>
173      *   For example: a tile map (cc.TMXMap) or a label with lots of characters (BitmapFontAtlas)<br/>
174      * </p>
175      * @param {cc.Sprite} sprite
176      * @param {Number} index
177      */
178     addQuadFromSprite:function (sprite, index) {
179         cc.Assert(sprite != null, "SpriteBatchNode.addQuadFromSprite():Argument must be non-nil");
180         cc.Assert((sprite instanceof cc.Sprite), "cc.SpriteBatchNode only supports cc.Sprites as children");
181 
182         /*while(index >= this._textureAtlas.getCapacity() || this._textureAtlas.getCapacity() == this._textureAtlas.getTotalQuads()){
183          this.increaseAtlasCapacity();
184          }*/
185         //todo fixed
186         //
187         // update the quad directly. Don't add the sprite to the scene graph
188         //
189         sprite.setBatchNode(this);
190         sprite.setAtlasIndex(index);
191 
192         this._textureAtlas.insertQuad(sprite.getQuad(), index);
193 
194         // XXX: updateTransform will update the textureAtlas too using updateQuad.
195         // XXX: so, it should be AFTER the insertQuad
196         sprite.setDirty(true);
197         sprite.updateTransform();
198 
199         if (cc.renderContextType == cc.CANVAS) {
200             this._children = cc.ArrayAppendObjectToIndex(this._children, sprite, index);
201         }
202     },
203 
204     /**
205      * <p>
206      *    This is the opposite of "addQuadFromSprite.<br/>
207      *    It add the sprite to the children and descendants array, but it doesn't update add it to the texture atlas<br/>
208      * </p>
209      * @param {cc.Node} child
210      * @param {Number} z zOrder
211      * @param {Number} aTag
212      * @return {cc.SpriteBatchNode}
213      */
214     addSpriteWithoutQuad:function (child, z, aTag) {
215         cc.Assert(child != null, "SpriteBatchNode.addQuadFromSprite():Argument must be non-nil");
216         cc.Assert((child instanceof cc.Sprite), "cc.SpriteBatchNode only supports cc.Sprites as children");
217 
218         // quad index is Z
219         child.setAtlasIndex(z);
220 
221         // XXX: optimize with a binary search
222         var i = 0;
223 
224         if (this._descendants && this._descendants.length > 0) {
225             var obj = null;
226             for (var index = 0; index < this._descendants.length; index++) {
227                 obj = this._descendants[index];
228                 if (obj && (obj.getAtlasIndex() >= z)) {
229                     ++i;
230                 }
231             }
232         }
233         this._descendants = cc.ArrayAppendObjectToIndex(this._descendants, child, i);
234 
235         // IMPORTANT: Call super, and not self. Avoid adding it to the texture atlas array
236         this.addChild(child, z, aTag);
237 
238         //#issue 1262 don't use lazy sorting, tiles are added as quads not as sprites, so sprites need to be added in order
239         this.reorderBatch(false);
240 
241         return this;
242     },
243 
244     // property
245     /**
246      * Return TextureAtlas of cc.SpriteBatchNode
247      * @return {cc.TextureAtlas}
248      */
249     getTextureAtlas:function () {
250         return this._textureAtlas;
251     },
252 
253     /**
254      * TextureAtlas of cc.SpriteBatchNode setter
255      * @param {cc.TextureAtlas} textureAtlas
256      */
257     setTextureAtlas:function (textureAtlas) {
258         if (textureAtlas != this._textureAtlas) {
259             this._textureAtlas = textureAtlas;
260         }
261     },
262 
263     /**
264      * Return Descendants of cc.SpriteBatchNode
265      * @return {Array}
266      */
267     getDescendants:function () {
268         return  this._descendants;
269     },
270 
271     /**
272      * <p>
273      *    initializes a CCSpriteBatchNode with a texture2d and capacity of children.<br/>
274      *    The capacity will be increased in 33% in runtime if it run out of space.
275      * </p>
276      * @param {cc.Texture2D} tex
277      * @param {Number} capacity
278      * @return {Boolean}
279      */
280     initWithTexture:function (tex, capacity) {
281         this._children = [];
282         this._descendants = [];
283 
284         this._blendFunc.src = cc.BLEND_SRC;
285         this._blendFunc.dst = cc.BLEND_DST;
286         this._textureAtlas = new cc.TextureAtlas();
287         capacity = capacity || cc.DEFAULT_SPRITE_BATCH_CAPACITY;
288 
289         this._textureAtlas.initWithTexture(tex, capacity);
290         if (cc.renderContextType == cc.CANVAS) {
291             this._originalTexture = tex;
292         }
293         if (cc.renderContextType == cc.WEBGL) {
294             this._updateBlendFunc();
295             //this.setShaderProgram(cc.ShaderCache.getInstance().programForKey(cc.Shader_PositionTextureColor)) ;
296         }
297         return true;
298     },
299 
300     /**
301      * set this node is dirty ,need redraw
302      */
303     setNodeDirty:function () {
304         this._setNodeDirtyForCache();
305         this._transformDirty = this._inverseDirty = true;
306         if (cc.NODE_TRANSFORM_USING_AFFINE_MATRIX) {
307             this._transformGLDirty = true;
308         }
309     },
310 
311     _setNodeDirtyForCache:function () {
312         this._cacheDirty = true;
313     },
314 
315     /**
316      * <p>
317      *    initializes a cc.SpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.<br/>
318      *    The capacity will be increased in 33% in runtime if it run out of space.<br/>
319      *    The file will be loaded using the TextureMgr.
320      * </p>
321      * @param {String} fileImage
322      * @param {Number} capacity
323      * @return {Boolean}
324      */
325     init:function (fileImage, capacity) {
326         var texture2D = cc.TextureCache.getInstance().textureForKey(fileImage);
327         if (!texture2D)
328             texture2D = cc.TextureCache.getInstance().addImage(fileImage);
329         return this.initWithTexture(texture2D, capacity);
330     },
331 
332     /**
333      * increase Atlas Capacity
334      */
335     increaseAtlasCapacity:function () {
336         // if we're going beyond the current TextureAtlas's capacity,
337         // all the previously initialized sprites will need to redo their texture coords
338         // this is likely computationally expensive
339         var quantity = (this._textureAtlas.getCapacity() + 1) * 4 / 3;
340 
341         cc.log("cocos2d: CCSpriteBatchNode: resizing TextureAtlas capacity from " + this._textureAtlas.getCapacity() + " to [" + quantity + "].");
342 
343         if (!this._textureAtlas.resizeCapacity(quantity)) {
344             // serious problems
345             cc.log("cocos2d: WARNING: Not enough memory to resize the atlas");
346             cc.Assert(false, "Not enough memory to resize the atla");
347         }
348     },
349 
350     /**
351      * removes a child given a certain index. It will also cleanup the running actions depending on the cleanup parameter.
352      * @warning Removing a child from a CCSpriteBatchNode is very slow
353      * @param {Number} index
354      * @param {Boolean} doCleanup
355      */
356     removeChildAtIndex:function (index, doCleanup) {
357         this.removeChild(this._children[index], doCleanup);
358     },
359 
360     /**
361      * add child helper
362      * @param {cc.Sprite} sprite
363      * @param {Number} index
364      */
365     insertChild:function (sprite, index) {
366         sprite.setBatchNode(this);
367         sprite.setAtlasIndex(index);
368         sprite.setDirty(true);
369 
370         if (this._textureAtlas.getTotalQuads() == this._textureAtlas.getCapacity()) {
371             this.increaseAtlasCapacity();
372         }
373 
374         this._textureAtlas.insertQuad(sprite.getQuad(), index);
375 
376         this._descendants = cc.ArrayAppendObjectToIndex(this._descendants, sprite, index);
377 
378         // update indices
379         var i = index + 1;
380         if (this._descendants && this._descendants.length > 0) {
381             for (; i < this._descendants.length; i++) {
382                 this._descendants[i].setAtlasIndex(this._descendants[i].getAtlasIndex() + 1);
383             }
384         }
385 
386         // add children recursively
387         var children = sprite.getChildren();
388         if (children && children.length > 0) {
389             for (i = 0; i < children.length; i++) {
390                 if (children[i]) {
391                     var getIndex = this.atlasIndexForChild(children[i], children[i].getZOrder());
392                     this.insertChild(children[i], getIndex);
393                 }
394             }
395         }
396     },
397 
398     /**
399      * addChild helper, faster than insertChild
400      * @param {cc.Sprite} sprite
401      */
402     appendChild:function (sprite) {
403         this._reorderChildDirty = true;
404         sprite.setBatchNode(this);
405         sprite.setDirty(true);
406 
407         if (this._textureAtlas.getTotalQuads() == this._textureAtlas.getCapacity()) {
408             this.increaseAtlasCapacity();
409         }
410 
411         cc.ArrayAppendObject(this._descendants, sprite);
412 
413         var index = this._descendants.length - 1;
414 
415         sprite.setAtlasIndex(index);
416 
417         this._textureAtlas.insertQuad(sprite.getQuad(), index);
418 
419         // add children recursively
420         var children = sprite.getChildren();
421         for (var i = 0; i < children.length; i++) {
422             this.appendChild(children[i]);
423         }
424     },
425 
426     /**
427      * remove sprite from TextureAtlas
428      * @param {cc.Sprite} sprite
429      */
430     removeSpriteFromAtlas:function (sprite) {
431         // remove from TextureAtlas
432         this._textureAtlas.removeQuadAtIndex(sprite.getAtlasIndex());
433 
434         // Cleanup sprite. It might be reused (issue #569)
435         sprite.setBatchNode(null);
436 
437         var index = cc.ArrayGetIndexOfObject(this._descendants, sprite);
438         if (index != -1) {
439             cc.ArrayRemoveObjectAtIndex(this._descendants, index);
440 
441             // update all sprites beyond this one
442             var len = this._descendants.length;
443             for (; index < len; ++index) {
444                 var s = this._descendants[index];
445                 s.setAtlasIndex(s.getAtlasIndex() - 1);
446             }
447         }
448 
449         // remove children recursively
450         var children = sprite.getChildren();
451         if (children && children.length > 0) {
452             for (var i = 0; i < children.length; i++) {
453                 if (children[i]) {
454                     this.removeSpriteFromAtlas(children[i]);
455                 }
456             }
457         }
458     },
459 
460     /**
461      * rebuild index in order for child
462      * @param {cc.Sprite} pobParent
463      * @param {Number} index
464      * @return {Number}
465      */
466     rebuildIndexInOrder:function (pobParent, index) {
467         var children = pobParent.getChildren();
468 
469         if (children && children.length > 0) {
470             for (var i = 0; i < children.length; i++) {
471                 var obj = children[i];
472                 if (obj && (obj.getZOrder() < 0)) {
473                     index = this.rebuildIndexInOrder(obj, index);
474                 }
475             }
476         }
477 
478         // ignore self (batch node)
479         if (!pobParent.isEqual(this)) {
480             pobParent.setAtlasIndex(index);
481             index++;
482         }
483 
484         if (children && children.length > 0) {
485             for (i = 0; i < children.length; i++) {
486                 obj = children[i];
487                 if (obj && (obj.getZOrder() >= 0)) {
488                     index = this.rebuildIndexInOrder(obj, index);
489                 }
490             }
491         }
492 
493         return index;
494     },
495 
496     /**
497      * get highest atlas index in child
498      * @param {cc.Sprite} sprite
499      * @return {Number}
500      */
501     highestAtlasIndexInChild:function (sprite) {
502         var children = sprite.getChildren();
503 
504         if (!children || children.length == 0) {
505             return sprite.getAtlasIndex();
506         } else {
507             return this.highestAtlasIndexInChild(children.pop());
508         }
509     },
510 
511     /**
512      * get lowest atlas index in child
513      * @param {cc.Sprite} sprite
514      * @return {Number}
515      */
516     lowestAtlasIndexInChild:function (sprite) {
517         var children = sprite.getChildren();
518 
519         if (!children || children.length == 0) {
520             return sprite.getAtlasIndex();
521         } else {
522             return this.lowestAtlasIndexInChild(children.pop());
523         }
524     },
525 
526     /**
527      * get atlas index for child
528      * @param {cc.Sprite} sprite
529      * @param {Number} nZ
530      * @return {Number}
531      */
532     atlasIndexForChild:function (sprite, nZ) {
533         var brothers = sprite.getParent().getChildren();
534         var childIndex = cc.ArrayGetIndexOfObject(brothers, sprite);
535 
536         // ignore parent Z if parent is spriteSheet
537         var ignoreParent = sprite.getParent() == this;
538         var previous = null;
539         if (childIndex > 0 && childIndex < cc.UINT_MAX) {
540             previous = brothers[childIndex - 1];
541         }
542 
543         // first child of the sprite sheet
544         if (ignoreParent) {
545             if (childIndex == 0) {
546                 return 0;
547             }
548             return this.highestAtlasIndexInChild(previous) + 1;
549         }
550 
551         // parent is a CCSprite, so, it must be taken into account
552         // first child of an CCSprite ?
553         if (childIndex == 0) {
554             var p = sprite.getParent();
555 
556             // less than parent and brothers
557             if (nZ < 0) {
558                 return p.getAtlasIndex();
559             } else {
560                 return p.getAtlasIndex() + 1;
561             }
562         } else {
563             // previous & sprite belong to the same branch
564             if ((previous.getZOrder() < 0 && nZ < 0) || (previous.getZOrder() >= 0 && nZ >= 0)) {
565                 return this.highestAtlasIndexInChild(previous) + 1;
566             }
567 
568             // else (previous < 0 and sprite >= 0 )
569             var p = sprite.getParent();
570             return p.getAtlasIndex() + 1;
571         }
572 
573         // Should not happen. Error calculating Z on SpriteSheet
574         cc.Assert(0, "CCSpriteBatchNode.atlasIndexForChild():should not run here");
575         return 0;
576     },
577 
578     /**
579      * Sprites use this to start sortChildren, don't call this manually
580      * @param {Boolean} reorder
581      */
582     reorderBatch:function (reorder) {
583         this._reorderChildDirty = reorder;
584     },
585 
586     // CCTextureProtocol
587     /**
588      * Return texture of cc.SpriteBatchNode
589      * @return {cc.Texture2D}
590      */
591     getTexture:function () {
592         return this._textureAtlas.getTexture();
593     },
594 
595     /**
596      * texture of cc.SpriteBatchNode setter
597      * @param {cc.Texture2D} texture
598      */
599     setTexture:function (texture) {
600         this._textureAtlas.setTexture(texture);
601         for (var i = 0; i < this._children.length; i++) {
602             this._children[i].setTexture(texture);
603         }
604         //this._updateBlendFunc();
605     },
606 
607     /**
608      * set the source blending function for the texture
609      * @param {Number} src
610      * @param {Number} dst
611      */
612     setBlendFunc:function (src, dst) {
613         if(arguments.length == 1)
614             this._blendFunc = src;
615         else
616             this._blendFunc = {src:src, dst:dst};
617     },
618 
619     /**
620      * returns the blending function used for the texture
621      * @return {cc.BlendFunc}
622      */
623     getBlendFunc:function () {
624         return this._blendFunc;
625     },
626 
627     /**
628      * don't call visit on it's children ( override visit of cc.Node )
629      * @override
630      * @param {CanvasContext} ctx
631      */
632     visit:function (ctx) {
633         if (cc.renderContextType == cc.CANVAS) {
634             var context = ctx || cc.renderContext;
635             // quick return if not visible
636             if (!this._visible) {
637                 return;
638             }
639             context.save();
640             this.transform(ctx);
641             var i;
642             if (this._isUseCache) {
643                 if (this._cacheDirty) {
644                     //add dirty region
645                     this._renderTexture.clear();
646                     this._renderTexture.context.save();
647                     this._renderTexture.context.translate(this._anchorPointInPoints.x, -(this._anchorPointInPoints.y ));
648                     if (this._children) {
649                         this.sortAllChildren();
650                         for (i = 0; i < this._children.length; i++) {
651                             if (this._children[i]) {
652                                 this._children[i].visit(this._renderTexture.context);
653                             }
654                         }
655                     }
656                     this._renderTexture.context.restore();
657                     this._cacheDirty = false;
658                 }
659                 // draw RenderTexture
660                 this.draw(ctx);
661             } else {
662                 if (this._children) {
663                     this.sortAllChildren();
664                     for (i = 0; i < this._children.length; i++) {
665                         if (this._children[i]) {
666                             this._children[i].visit(context);
667                         }
668                     }
669                 }
670             }
671             context.restore();
672         } else {
673             //TODO
674             //cc.PROFILER_START_CATEGORY(kCCProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit");
675 
676             // CAREFUL:
677             // This visit is almost identical to CocosNode#visit
678             // with the exception that it doesn't call visit on it's children
679             //
680             // The alternative is to have a void CCSprite#visit, but
681             // although this is less mantainable, is faster
682             //
683             if (!this._visible) {
684                 return;
685             }
686 
687             //kmGLPushMatrix();
688 
689             if (this._grid && this._grid.isActive()) {
690                 this._grid.beforeDraw();
691                 this.transformAncestors();
692             }
693 
694             this.sortAllChildren();
695             this.transform();
696 
697             this.draw();
698 
699             if (this._grid && this._grid.isActive()) {
700                 this._grid.afterDraw(this);
701             }
702 
703             //kmGLPopMatrix();
704             this.setOrderOfArrival(0);
705 
706             //cc.PROFILER_STOP_CATEGORY(kCCProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit");
707         }
708     },
709 
710     /**
711      * add child to cc.SpriteBatchNode (override addChild of cc.Node)
712      * @override
713      * @param {cc.Sprite} child
714      * @param {Number} zOrder
715      * @param {Number} tag
716      */
717     addChild:function (child, zOrder, tag) {
718         switch (arguments.length) {
719             case 1:
720                 this._super(child);
721                 break;
722             case 2:
723                 this._super(child, zOrder);
724                 break;
725             case 3:
726                 cc.Assert(child != null, "SpriteBatchNode.addChild():child should not be null");
727                 cc.Assert((child instanceof cc.Sprite), "cc.SpriteBatchNode only supports cc.Sprites as children");
728 
729                 // check CCSprite is using the same texture id
730                 if (cc.renderContextType != cc.CANVAS) {
731                     cc.Assert(child.getTexture().getName() == this._textureAtlas.getTexture().getName(),
732                         "SpriteBatchNode.addChild():check cc.Sprite is using the same texture id");
733                 }
734                 this._super(child, zOrder, tag);
735                 this.appendChild(child);
736                 break;
737             case 4:
738                 if (arguments[3]) {
739                     this._super(child, zOrder, tag);
740                 }
741                 break;
742             default:
743                 throw "Argument must be non-nil ";
744                 break;
745         }
746 
747         //this._addDirtyRegionToDirector(this.getBoundingBoxToWorld());
748         this.setNodeDirty();
749     },
750 
751     /**
752      *  (override reorderChild of cc.Node)
753      * @override
754      * @param {cc.Sprite} child
755      * @param {Number} zOrder
756      */
757     reorderChild:function (child, zOrder) {
758         cc.Assert(child != null, "SpriteBatchNode.addChild():the child should not be null");
759         cc.Assert(this._children.indexOf(child) > -1, "SpriteBatchNode.addChild():Child doesn't belong to Sprite");
760 
761         if (zOrder == child.getZOrder()) {
762             return;
763         }
764 
765         //save dirty region when before change
766         //this._addDirtyRegionToDirector(this.getBoundingBoxToWorld());
767 
768         //set the z-order and sort later
769         this._super(child, zOrder);
770 
771         //save dirty region when after changed
772         //this._addDirtyRegionToDirector(this.getBoundingBoxToWorld());
773         this.setNodeDirty();
774     },
775 
776     /**
777      * remove child from cc.SpriteBatchNode (override removeChild of cc.Node)
778      * @param {cc.Sprite} child
779      * @param cleanup
780      */
781     removeChild:function (child, cleanup) {
782         // explicit null handling
783         if (child == null) {
784             return;
785         }
786         cc.Assert(this._children.indexOf(child) > -1, "SpriteBatchNode.addChild():sprite batch node should contain the child");
787 
788         // cleanup before removing
789         this.removeSpriteFromAtlas(child);
790 
791         this._super(child, cleanup);
792     },
793 
794     /**
795      * <p>Removes all children from the container and do a cleanup all running actions depending on the cleanup parameter. <br/>
796      * (override removeAllChildren of cc.Node)</p>
797      * @param {Boolean} cleanup
798      */
799     removeAllChildren:function (cleanup) {
800         // Invalidate atlas index. issue #569
801         // useSelfRender should be performed on all descendants. issue #1216
802         var i;
803         if (this._descendants && this._descendants.length > 0) {
804             for (i = 0; i < this._descendants.length; i++) {
805                 if (this._descendants[i]) {
806                     this._descendants[i].setBatchNode(null);
807                 }
808             }
809         }
810 
811         this._super(cleanup);
812         this._descendants = [];
813         this._textureAtlas.removeAllQuads();
814     },
815 
816     sortAllChildren:function () {
817         if (this._reorderChildDirty) {
818             var i = 0, j = 0, length = this._children.length;
819             //insertion sort
820             for (i = 1; i < length; i++) {
821                 var tempItem = this._children[i];
822                 j = i - 1;
823 
824                 //continue moving element downwards while zOrder is smaller or when zOrder is the same but orderOfArrival is smaller
825                 while (j >= 0 && (tempItem.getZOrder() < this._children[j].getZOrder() ||
826                     (tempItem.getZOrder() == this._children[j].getZOrder() && tempItem.getOrderOfArrival() < this._children[j].getOrderOfArrival()))) {
827                     this._children[j + 1] = this._children[j];
828                     j--;
829                 }
830                 this._children[j + 1] = tempItem;
831             }
832 
833             //sorted now check all children
834             if (this._children.length > 0) {
835                 //first sort all children recursively based on zOrder
836                 this._arrayMakeObjectsPerformSelector(this._children, cc.Node.StateCallbackType.sortAllChildren);
837 
838                 var index = 0;
839                 //fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact)
840                 // and at the same time reorder descedants and the quads to the right index
841                 if (cc.renderContextType == cc.WEBGL) {
842                     for (i = 0; i < this._children.length; i++) {
843                         index = this._updateAtlasIndex(this._children[i], index);
844                     }
845                 }
846             }
847 
848             this._reorderChildDirty = false;
849         }
850     },
851 
852     /**
853      * draw cc.SpriteBatchNode (override draw of cc.Node)
854      * @param {CanvasContext} ctx
855      */
856     draw:function (ctx) {
857         //cc.PROFILER_START("cc.SpriteBatchNode - draw");
858         this._super();
859 
860         if (cc.renderContextType == cc.CANVAS) {
861             var context = ctx || cc.renderContext;
862             //context.globalAlpha = this._opacity / 255;
863             var pos = cc.p(0 | ( -this._anchorPointInPoints.x), 0 | ( -this._anchorPointInPoints.y));
864             if (this._renderTexture) {
865                 //direct draw image by canvas drawImage
866                 context.drawImage(this._renderTexture.getCanvas(), pos.x, -(pos.y + this._renderTexture.getCanvas().height));
867             }
868         } else {
869             // Optimization: Fast Dispatch
870             if (this._textureAtlas.getTotalQuads() == 0) {
871                 return;
872             }
873 
874             //cc.NODE_DRAW_SETUP();
875 
876             this._arrayMakeObjectsPerformSelector(this._children, cc.Node.StateCallbackType.updateTransform);
877 
878             //ccGLBlendFunc( m_blendFunc.src, m_blendFunc.dst );
879 
880             this._textureAtlas.drawQuads();
881 
882             //cc.PROFILER_STOP("CCSpriteBatchNode - draw");
883         }
884     }
885 });
886 
887 /**
888  * <p>
889  *    creates a CCSpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) with a default capacity of 29 children.<br/>
890  *    The capacity will be increased in 33% in runtime if it run out of space.<br/>
891  *    The file will be loaded using the TextureMgr.<br/>
892  * </p>
893  * @param {String} fileImage
894  * @param {Number} capacity
895  * @return {cc.SpriteBatchNode}
896  * @example
897  * //create a SpriteBatchNode
898  * var parent2 = cc.SpriteBatchNode.create("res/animations/grossini.png", 50);
899  */
900 cc.SpriteBatchNode.create = function (fileImage, capacity) {
901     if (!capacity) {
902         capacity = cc.DEFAULT_SPRITE_BATCH_CAPACITY;
903     }
904 
905     var batchNode = new cc.SpriteBatchNode();
906     batchNode.init(fileImage, capacity);
907 
908     return batchNode;
909 };
910 
911 /**
912  * <p>
913  *   creates a CCSpriteBatchNode with a texture2d and a default capacity of 29 children.<br/>
914  *   The capacity will be increased in 33% in runtime if it run out of space.<br/>
915  * </p>
916  * @param {cc.Texture2D} texture
917  * @param {Number} capacity
918  * @return {cc.SpriteBatchNode}
919  */
920 cc.SpriteBatchNode.createWithTexture = function (texture, capacity) {
921     if (!capacity) {
922         capacity = cc.DEFAULT_SPRITE_BATCH_CAPACITY;
923     }
924 
925     var batchNode = new cc.SpriteBatchNode();
926     batchNode.initWithTexture(texture, capacity);
927 
928     return batchNode;
929 };
930