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  * <p>cc.TMXLayer represents the TMX layer. </p>
 29  *
 30  * <p>It is a subclass of cc.SpriteBatchNode. By default the tiles are rendered using a cc.TextureAtlas. <br />
 31  * If you modify a tile on runtime, then, that tile will become a cc.Sprite, otherwise no cc.Sprite objects are created. <br />
 32  * The benefits of using cc.Sprite objects as tiles are: <br />
 33  * - tiles (cc.Sprite) can be rotated/scaled/moved with a nice API </p>
 34  *
 35  * <p>If the layer contains a property named "cc.vertexz" with an integer (in can be positive or negative), <br />
 36  * then all the tiles belonging to the layer will use that value as their OpenGL vertex Z for depth. </p>
 37  *
 38  * <p>On the other hand, if the "cc.vertexz" property has the "automatic" value, then the tiles will use an automatic vertex Z value. <br />
 39  * Also before drawing the tiles, GL_ALPHA_TEST will be enabled, and disabled after drawing them. The used alpha func will be:  </p>
 40  *
 41  * glAlphaFunc( GL_GREATER, value ) <br />
 42  *
 43  * <p>"value" by default is 0, but you can change it from Tiled by adding the "cc_alpha_func" property to the layer. <br />
 44  * The value 0 should work for most cases, but if you have tiles that are semi-transparent, then you might want to use a different value, like 0.5.</p>
 45  * @class
 46  * @extends cc.SpriteBatchNode
 47  */
 48 cc.TMXLayer = cc.SpriteBatchNode.extend(/** @lends cc.TMXLayer# */{
 49     //size of the layer in tiles
 50     _layerSize: null,
 51     _mapTileSize: null,
 52     _tiles: null,
 53     _tileSet: null,
 54     _layerOrientation: null,
 55     _properties: null,
 56     //name of the layer
 57     _layerName: "",
 58     //TMX Layer supports opacity
 59     _opacity: 255,
 60     _minGID: null,
 61     _maxGID: null,
 62     //Only used when vertexZ is used
 63     _vertexZvalue: null,
 64     _useAutomaticVertexZ: null,
 65     _alphaFuncValue: null,
 66     //used for optimization
 67     _reusedTile: null,
 68     _atlasIndexArray: null,
 69     //used for retina display
 70     _contentScaleFactor: null,
 71 
 72     _cacheCanvas:null,
 73     _cacheContext:null,
 74     _cacheTexture:null,
 75 
 76     /**
 77      *  Constructor
 78      */
 79     ctor:function () {
 80         cc.SpriteBatchNode.prototype.ctor.call(this);
 81         this._children = [];
 82         this._descendants = [];
 83 
 84         this._layerSize = cc.SizeZero();
 85         this._mapTileSize = cc.SizeZero();
 86 
 87         if(cc.renderContextType === cc.CANVAS){
 88             var locCanvas = cc.canvas;
 89             var tmpCanvas = document.createElement('canvas');
 90             tmpCanvas.width = locCanvas.width;
 91             tmpCanvas.height = locCanvas.height;
 92             this._cacheCanvas = tmpCanvas;
 93             this._cacheContext = this._cacheCanvas.getContext('2d');
 94             var tempTexture = new cc.Texture2D();
 95             tempTexture.initWithElement(tmpCanvas);
 96             tempTexture.handleLoadedTexture();
 97             this._cacheTexture = tempTexture;
 98             this.setContentSize(cc.size(locCanvas.width, locCanvas.height));
 99         }
100     },
101 
102     setContentSize:function (size) {
103         if (!size)
104             return;
105         cc.Node.prototype.setContentSize.call(this, size);
106 
107         if(cc.renderContextType === cc.CANVAS){
108             var eglViewer = cc.EGLView.getInstance();
109             var locCanvas = this._cacheCanvas;
110             locCanvas.width = 0|(size.width * 1.5 * eglViewer._scaleX);
111             locCanvas.height = 0|(size.height * 1.5 * eglViewer._scaleY);
112             this._cacheContext.translate(0, locCanvas.height);
113             var locContentSize = this._cacheTexture._contentSize;
114             locContentSize.width = locCanvas.width;
115             locContentSize.height = locCanvas.height;
116         }
117     },
118 
119     /**
120      * Return texture of cc.SpriteBatchNode
121      * @return {cc.Texture2D}
122      */
123     getTexture:function () {
124         if(cc.renderContextType === cc.CANVAS)
125             return this._cacheTexture;
126         else
127             return cc.SpriteBatchNode.prototype.getTexture.call(this);
128     },
129 
130     /**
131      * don't call visit on it's children ( override visit of cc.Node )
132      * @override
133      * @param {CanvasRenderingContext2D} ctx
134      */
135     visit:function (ctx) {
136         if (cc.renderContextType === cc.WEBGL) {
137             cc.SpriteBatchNode.prototype.visit.call(this, ctx);
138             return;
139         }
140         var context = ctx || cc.renderContext;
141         // quick return if not visible
142         if (!this._visible)
143             return;
144 
145         context.save();
146         this.transform(ctx);
147         var i, locChildren = this._children;
148 
149         if (this._cacheDirty) {
150             //add dirty region
151             var locCacheContext = this._cacheContext, locCacheCanvas = this._cacheCanvas;
152             locCacheContext.clearRect(0, 0, locCacheCanvas.width, -locCacheCanvas.height);
153             locCacheContext.save();
154             locCacheContext.translate(this._anchorPointInPoints.x, -(this._anchorPointInPoints.y ));
155             if (locChildren) {
156                 this.sortAllChildren();
157                 for (i = 0; i < locChildren.length; i++) {
158                     if (locChildren[i])
159                         locChildren[i].visit(locCacheContext);
160                 }
161             }
162             locCacheContext.restore();
163             this._cacheDirty = false;
164         }
165         // draw RenderTexture
166         this.draw(ctx);
167         context.restore();
168     },
169 
170     /**
171      * draw cc.SpriteBatchNode (override draw of cc.Node)
172      * @param {CanvasRenderingContext2D} ctx
173      */
174     draw:null,
175 
176     _drawForCanvas:function (ctx) {
177         var context = ctx || cc.renderContext;
178         //context.globalAlpha = this._opacity / 255;
179         var posX = 0 | ( -this._anchorPointInPoints.x), posY = 0 | ( -this._anchorPointInPoints.y);
180         var locCacheCanvas = this._cacheCanvas;
181         //direct draw image by canvas drawImage
182         if (locCacheCanvas)
183             context.drawImage(locCacheCanvas, posX, -(posY + locCacheCanvas.height));
184     },
185 
186     /**
187      * @return {cc.Size}
188      */
189     getLayerSize:function () {
190         return cc.size(this._layerSize.width, this._layerSize.height);
191     },
192 
193     /**
194      * @param {cc.Size} Var
195      */
196     setLayerSize:function (Var) {
197         this._layerSize.width = Var.width;
198         this._layerSize.height = Var.height;
199     },
200 
201     /**
202      * Size of the map's tile (could be different from the tile's size)
203      * @return {cc.Size}
204      */
205     getMapTileSize:function () {
206         return cc.size(this._mapTileSize.width,this._mapTileSize.height);
207     },
208 
209     /**
210      * @param {cc.Size} Var
211      */
212     setMapTileSize:function (Var) {
213         this._mapTileSize.width = Var.width;
214         this._mapTileSize.height = Var.height;
215     },
216 
217     /**
218      * Pointer to the map of tiles
219      * @return {Array}
220      */
221     getTiles:function () {
222         return this._tiles;
223     },
224 
225     /**
226      * @param {Array} Var
227      */
228     setTiles:function (Var) {
229         this._tiles = Var;
230     },
231 
232     /**
233      * Tile set information for the layer
234      * @return {cc.TMXTilesetInfo}
235      */
236     getTileSet:function () {
237         return this._tileSet;
238     },
239 
240     /**
241      * @param {cc.TMXTilesetInfo} Var
242      */
243     setTileSet:function (Var) {
244         this._tileSet = Var;
245     },
246 
247     /**
248      * Layer orientation, which is the same as the map orientation
249      * @return {Number}
250      */
251     getLayerOrientation:function () {
252         return this._layerOrientation;
253     },
254 
255     /**
256      * @param {Number} Var
257      */
258     setLayerOrientation:function (Var) {
259         this._layerOrientation = Var;
260     },
261 
262     /**
263      * properties from the layer. They can be added using Tiled
264      * @return {Array}
265      */
266     getProperties:function () {
267         return this._properties;
268     },
269 
270     /**
271      * @param {Array} Var
272      */
273     setProperties:function (Var) {
274         this._properties = Var;
275     },
276 
277     /**
278      * Initializes a cc.TMXLayer with a tileset info, a layer info and a map info
279      * @param {cc.TMXTilesetInfo} tilesetInfo
280      * @param {cc.TMXLayerInfo} layerInfo
281      * @param {cc.TMXMapInfo} mapInfo
282      * @return {Boolean}
283      */
284     initWithTilesetInfo:function (tilesetInfo, layerInfo, mapInfo) {
285         // XXX: is 35% a good estimate ?
286         var size = layerInfo._layerSize;
287         var totalNumberOfTiles = parseInt(size.width * size.height);
288         var capacity = totalNumberOfTiles * 0.35 + 1; // 35 percent is occupied ?
289         var texture;
290         if (tilesetInfo)
291             texture = cc.TextureCache.getInstance().addImage(tilesetInfo.sourceImage);
292 
293         if (this.initWithTexture(texture, capacity)) {
294             // layerInfo
295             this._layerName = layerInfo.name;
296             this._layerSize = size;
297             this._tiles = layerInfo._tiles;
298             this._minGID = layerInfo._minGID;
299             this._maxGID = layerInfo._maxGID;
300             this._opacity = layerInfo._opacity;
301             this.setProperties(layerInfo.getProperties());
302             this._contentScaleFactor = cc.Director.getInstance().getContentScaleFactor();
303 
304             // tilesetInfo
305             this._tileSet = tilesetInfo;
306 
307             // mapInfo
308             this._mapTileSize = mapInfo.getTileSize();
309             this._layerOrientation = mapInfo.getOrientation();
310 
311             // offset (after layer orientation is set);
312             var offset = this._calculateLayerOffset(layerInfo.offset);
313             this.setPosition(cc.POINT_PIXELS_TO_POINTS(offset));
314 
315             this._atlasIndexArray = [];
316             this.setContentSize(cc.SIZE_PIXELS_TO_POINTS(cc.size(this._layerSize.width * this._mapTileSize.width,
317                 this._layerSize.height * this._mapTileSize.height)));
318             this._useAutomaticVertexZ = false;
319             this._vertexZvalue = 0;
320             return true;
321         }
322         return false;
323     },
324 
325     /**
326      * <p>Dealloc the map that contains the tile position from memory. <br />
327      * Unless you want to know at runtime the tiles positions, you can safely call this method. <br />
328      * If you are going to call layer.getTileGIDAt() then, don't release the map</p>
329      */
330     releaseMap:function () {
331         if (this._tiles)
332             this._tiles = null;
333 
334         if (this._atlasIndexArray)
335             this._atlasIndexArray = null;
336     },
337 
338     /**
339      * <p>Returns the tile (cc.Sprite) at a given a tile coordinate. <br/>
340      * The returned cc.Sprite will be already added to the cc.TMXLayer. Don't add it again.<br/>
341      * The cc.Sprite can be treated like any other cc.Sprite: rotated, scaled, translated, opacity, color, etc. <br/>
342      * You can remove either by calling: <br/>
343      * - layer.removeChild(sprite, cleanup); <br/>
344      * - or layer.removeTileAt(ccp(x,y)); </p>
345      * @param {cc.Point} pos
346      * @return {cc.Sprite}
347      */
348     getTileAt: function (pos) {
349         if(!pos)
350             throw "cc.TMXLayer.getTileAt(): pos should be non-null";
351         if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0)
352             throw "cc.TMXLayer.getTileAt(): invalid position";
353         if(!this._tiles || !this._atlasIndexArray){
354             cc.log("cc.TMXLayer.getTileAt(): TMXLayer: the tiles map has been released");
355             return null;
356         }
357 
358         var tile = null;
359         var gid = this.getTileGIDAt(pos);
360 
361         // if GID == 0, then no tile is present
362         if (gid === 0)
363             return tile;
364 
365         var z = 0 | (pos.x + pos.y * this._layerSize.width);
366         tile = this.getChildByTag(z);
367         // tile not created yet. create it
368         if (!tile) {
369             var rect = this._tileSet.rectForGID(gid);
370             rect = cc.RECT_PIXELS_TO_POINTS(rect);
371 
372             tile = new cc.Sprite();
373             tile.initWithTexture(this.getTexture(), rect);
374             tile.setBatchNode(this);
375             tile.setPosition(this.getPositionAt(pos));
376             tile.setVertexZ(this._vertexZForPos(pos));
377             tile.setAnchorPoint(cc.PointZero());
378             tile.setOpacity(this._opacity);
379 
380             var indexForZ = this._atlasIndexForExistantZ(z);
381             this.addSpriteWithoutQuad(tile, indexForZ, z);
382         }
383         return tile;
384     },
385 
386     /**
387      * Returns the tile gid at a given tile coordinate. <br />
388      * if it returns 0, it means that the tile is empty. <br />
389      * This method requires the the tile map has not been previously released (eg. don't call layer.releaseMap())<br />
390      * @param {cc.Point} pos
391      * @return {Number}
392      */
393     getTileGIDAt:function (pos) {
394         if(!pos)
395             throw "cc.TMXLayer.getTileGIDAt(): pos should be non-null";
396         if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0)
397             throw "cc.TMXLayer.getTileGIDAt(): invalid position";
398         if(!this._tiles || !this._atlasIndexArray){
399             cc.log("cc.TMXLayer.getTileGIDAt(): TMXLayer: the tiles map has been released");
400             return null;
401         }
402 
403         var idx = 0 | (pos.x + pos.y * this._layerSize.width);
404         // Bits on the far end of the 32-bit global tile ID are used for tile flags
405         var tile = this._tiles[idx];
406 
407         return (tile & cc.TMX_TILE_FLIPPED_MASK) >>> 0;
408     },
409     // XXX: deprecated
410     // tileGIDAt:getTileGIDAt,
411 
412     /**
413      *  lipped tiles can be changed dynamically
414      * @param {cc.Point} pos
415      * @return {Number}
416      */
417     getTileFlagsAt:function (pos) {
418         if(!pos)
419             throw "cc.TMXLayer.getTileFlagsAt(): pos should be non-null";
420         if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0)
421             throw "cc.TMXLayer.getTileFlagsAt(): invalid position";
422         if(!this._tiles || !this._atlasIndexArray){
423             cc.log("cc.TMXLayer.getTileFlagsAt(): TMXLayer: the tiles map has been released");
424             return null;
425         }
426 
427         var idx = 0 | (pos.x + pos.y * this._layerSize.width);
428         // Bits on the far end of the 32-bit global tile ID are used for tile flags
429         var tile = this._tiles[idx];
430 
431         return (tile & cc.TMX_TILE_FLIPPED_ALL) >>> 0;
432     },
433     // XXX: deprecated
434     // tileFlagAt:getTileFlagsAt,
435 
436     /**
437      * <p>Sets the tile gid (gid = tile global id) at a given tile coordinate.<br />
438      * The Tile GID can be obtained by using the method "tileGIDAt" or by using the TMX editor . Tileset Mgr +1.<br />
439      * If a tile is already placed at that position, then it will be removed.</p>
440      * @param {Number} gid
441      * @param {cc.Point} pos
442      * @param {Number} flags
443      */
444     setTileGID:function (gid, pos, flags) {
445         if(!pos)
446             throw "cc.TMXLayer.setTileGID(): pos should be non-null";
447         if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0)
448             throw "cc.TMXLayer.setTileGID(): invalid position";
449         if(!this._tiles || !this._atlasIndexArray){
450             cc.log("cc.TMXLayer.setTileGID(): TMXLayer: the tiles map has been released");
451             return null;
452         }
453         if(gid !== 0 && gid < this._tileSet.firstGid){
454             cc.log( "cc.TMXLayer.setTileGID(): invalid gid:" + gid);
455             return null;
456         }
457 
458         flags = flags || 0;
459         this._setNodeDirtyForCache();
460 
461         var currentFlags = this.getTileFlagsAt(pos);
462         var currentGID = this.getTileGIDAt(pos);
463 
464         if (currentGID != gid || currentFlags != flags) {
465             var gidAndFlags = (gid | flags) >>> 0;
466             // setting gid=0 is equal to remove the tile
467             if (gid === 0)
468                 this.removeTileAt(pos);
469             else if (currentGID === 0)            // empty tile. create a new one
470                 this._insertTileForGID(gidAndFlags, pos);
471             else {                // modifying an existing tile with a non-empty tile
472                 var z = pos.x + pos.y * this._layerSize.width;
473                 var sprite = this.getChildByTag(z);
474                 if (sprite) {
475                     var rect = this._tileSet.rectForGID(gid);
476                     rect = cc.RECT_PIXELS_TO_POINTS(rect);
477 
478                     sprite.setTextureRect(rect, false, rect.size);
479                     if (flags != null)
480                         this._setupTileSprite(sprite, pos, gidAndFlags);
481 
482                     this._tiles[z] = gidAndFlags;
483                 } else
484                     this._updateTileForGID(gidAndFlags, pos);
485             }
486         }
487     },
488 
489     /**
490      * Removes a tile at given tile coordinate
491      * @param {cc.Point} pos
492      */
493     removeTileAt:function (pos) {
494         if(!pos)
495             throw "cc.TMXLayer.removeTileAt(): pos should be non-null";
496         if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0)
497             throw "cc.TMXLayer.removeTileAt(): invalid position";
498         if(!this._tiles || !this._atlasIndexArray){
499             cc.log("cc.TMXLayer.removeTileAt(): TMXLayer: the tiles map has been released");
500             return null;
501         }
502 
503         var gid = this.getTileGIDAt(pos);
504         if (gid !== 0) {
505             if (cc.renderContextType === cc.CANVAS)
506                 this._setNodeDirtyForCache();
507             var z = 0 | (pos.x + pos.y * this._layerSize.width);
508             var atlasIndex = this._atlasIndexForExistantZ(z);
509             // remove tile from GID map
510             this._tiles[z] = 0;
511 
512             // remove tile from atlas position array
513             cc.ArrayRemoveObjectAtIndex(this._atlasIndexArray, atlasIndex);
514 
515             // remove it from sprites and/or texture atlas
516             var sprite = this.getChildByTag(z);
517 
518             if (sprite)
519                 cc.SpriteBatchNode.prototype.removeChild.call(this, sprite, true);           //this.removeChild(sprite, true);
520             else {
521                 if(cc.renderContextType === cc.WEBGL)
522                     this._textureAtlas.removeQuadAtIndex(atlasIndex);
523 
524                 // update possible children
525                 if (this._children) {
526                     var locChildren = this._children;
527                     for (var i = 0, len = locChildren.length; i < len; i++) {
528                         var child = locChildren[i];
529                         if (child) {
530                             var ai = child.getAtlasIndex();
531                             if (ai >= atlasIndex)
532                                 child.setAtlasIndex(ai - 1);
533                         }
534                     }
535                 }
536             }
537         }
538     },
539 
540     /**
541      * Returns the position in pixels of a given tile coordinate
542      * @param {cc.Point} pos
543      * @return {cc.Point}
544      */
545     getPositionAt:function (pos) {
546         var ret = cc.PointZero();
547         switch (this._layerOrientation) {
548             case cc.TMX_ORIENTATION_ORTHO:
549                 ret = this._positionForOrthoAt(pos);
550                 break;
551             case cc.TMX_ORIENTATION_ISO:
552                 ret = this._positionForIsoAt(pos);
553                 break;
554             case cc.TMX_ORIENTATION_HEX:
555                 ret = this._positionForHexAt(pos);
556                 break;
557         }
558         return cc.POINT_PIXELS_TO_POINTS(ret);
559     },
560     // XXX: Deprecated. For backward compatibility only
561     // positionAt:getPositionAt,
562 
563     /**
564      * Return the value for the specific property name
565      * @param {String} propertyName
566      * @return {*}
567      */
568     getProperty:function (propertyName) {
569         return this._properties[propertyName];
570     },
571 
572     /**
573      * Creates the tiles
574      */
575     setupTiles:function () {
576         // Optimization: quick hack that sets the image size on the tileset
577         if (cc.renderContextType === cc.CANVAS) {
578             this._tileSet.imageSize = this._originalTexture.getContentSizeInPixels();
579         } else {
580             this._tileSet.imageSize = this._textureAtlas.getTexture().getContentSizeInPixels();
581 
582             // By default all the tiles are aliased
583             // pros:
584             //  - easier to render
585             // cons:
586             //  - difficult to scale / rotate / etc.
587             this._textureAtlas.getTexture().setAliasTexParameters();
588         }
589 
590         // Parse cocos2d properties
591         this._parseInternalProperties();
592         if (cc.renderContextType === cc.CANVAS)
593             this._setNodeDirtyForCache();
594 
595         var locLayerHeight = this._layerSize.height, locLayerWidth = this._layerSize.width;
596         for (var y = 0; y < locLayerHeight; y++) {
597             for (var x = 0; x < locLayerWidth; x++) {
598                 var pos = x + locLayerWidth * y;
599                 var gid = this._tiles[pos];
600 
601                 // XXX: gid == 0 -. empty tile
602                 if (gid !== 0) {
603                     this._appendTileForGID(gid, cc.p(x, y));
604                     // Optimization: update min and max GID rendered by the layer
605                     this._minGID = Math.min(gid, this._minGID);
606                     this._maxGID = Math.max(gid, this._maxGID);
607                 }
608             }
609         }
610 
611         if (!((this._maxGID >= this._tileSet.firstGid) && (this._minGID >= this._tileSet.firstGid))) {
612             cc.log("cocos2d:TMX: Only 1 tileset per layer is supported");
613         }
614     },
615 
616     /**
617      * cc.TMXLayer doesn't support adding a cc.Sprite manually.
618      * @warning addChild(child); is not supported on cc.TMXLayer. Instead of setTileGID.
619      * @param {cc.Node} child
620      * @param {number} zOrder
621      * @param {number} tag
622      */
623     addChild:function (child, zOrder, tag) {
624         cc.log("addChild: is not supported on cc.TMXLayer. Instead use setTileGID or tileAt.");
625     },
626 
627     /**
628      * Remove child
629      * @param  {cc.Sprite} sprite
630      * @param  {Boolean} cleanup
631      */
632     removeChild:function (sprite, cleanup) {
633         // allows removing nil objects
634         if (!sprite)
635             return;
636 
637         if(this._children.indexOf(sprite) === -1){
638             cc.log("cc.TMXLayer.removeChild(): Tile does not belong to TMXLayer");
639             return;
640         }
641 
642         if (cc.renderContextType === cc.CANVAS)
643             this._setNodeDirtyForCache();
644         var atlasIndex = sprite.getAtlasIndex();         //cc.ArrayGetIndexOfObject(this._children, sprite);
645         var zz = this._atlasIndexArray[atlasIndex];
646         this._tiles[zz] = 0;
647         cc.ArrayRemoveObjectAtIndex(this._atlasIndexArray, atlasIndex);
648         cc.SpriteBatchNode.prototype.removeChild.call(this, sprite, cleanup);
649     },
650 
651     /**
652      * @return {String}
653      */
654     getLayerName:function () {
655         return this._layerName.toString();
656     },
657 
658     /**
659      * @param {String} layerName
660      */
661     setLayerName:function (layerName) {
662         this._layerName = layerName;
663     },
664 
665     _positionForIsoAt:function (pos) {
666         return cc.p(this._mapTileSize.width / 2 * ( this._layerSize.width + pos.x - pos.y - 1),
667             this._mapTileSize.height / 2 * (( this._layerSize.height * 2 - pos.x - pos.y) - 2));
668     },
669 
670     _positionForOrthoAt:function (pos) {
671         return cc.p(pos.x * this._mapTileSize.width,
672             (this._layerSize.height - pos.y - 1) * this._mapTileSize.height);
673     },
674 
675     _positionForHexAt:function (pos) {
676         var diffY = (pos.x % 2 == 1) ? (-this._mapTileSize.height / 2) : 0;
677         return cc.p(pos.x * this._mapTileSize.width * 3 / 4,
678             (this._layerSize.height - pos.y - 1) * this._mapTileSize.height + diffY);
679     },
680 
681     _calculateLayerOffset:function (pos) {
682         var ret = cc.PointZero();
683         switch (this._layerOrientation) {
684             case cc.TMX_ORIENTATION_ORTHO:
685                 ret = cc.p(pos.x * this._mapTileSize.width, -pos.y * this._mapTileSize.height);
686                 break;
687             case cc.TMX_ORIENTATION_ISO:
688                 ret = cc.p((this._mapTileSize.width / 2) * (pos.x - pos.y),
689                     (this._mapTileSize.height / 2 ) * (-pos.x - pos.y));
690                 break;
691             case cc.TMX_ORIENTATION_HEX:
692                 if(pos.x !== 0 || pos.y !== 0)
693                     cc.log("offset for hexagonal map not implemented yet");
694                 break;
695         }
696         return ret;
697     },
698 
699     _appendTileForGID:function (gid, pos) {
700         var rect = this._tileSet.rectForGID(gid);
701         rect = cc.RECT_PIXELS_TO_POINTS(rect);
702 
703         var z = 0 | (pos.x + pos.y * this._layerSize.width);
704         var tile = this._reusedTileWithRect(rect);
705         this._setupTileSprite(tile, pos, gid);
706 
707         // optimization:
708         // The difference between appendTileForGID and insertTileforGID is that append is faster, since
709         // it appends the tile at the end of the texture atlas
710         var indexForZ = this._atlasIndexArray.length;
711 
712         // don't add it using the "standard" way.
713         this.insertQuadFromSprite(tile, indexForZ);
714 
715         // append should be after addQuadFromSprite since it modifies the quantity values
716         this._atlasIndexArray = cc.ArrayAppendObjectToIndex(this._atlasIndexArray, z, indexForZ);
717         return tile;
718     },
719 
720     _insertTileForGID:function (gid, pos) {
721         var rect = this._tileSet.rectForGID(gid);
722         rect = cc.RECT_PIXELS_TO_POINTS(rect);
723 
724         var z = 0 | (pos.x + pos.y * this._layerSize.width);
725         var tile = this._reusedTileWithRect(rect);
726         this._setupTileSprite(tile, pos, gid);
727 
728         // get atlas index
729         var indexForZ = this._atlasIndexForNewZ(z);
730 
731         // Optimization: add the quad without adding a child
732         this.insertQuadFromSprite(tile, indexForZ);
733 
734         // insert it into the local atlasindex array
735         this._atlasIndexArray = cc.ArrayAppendObjectToIndex(this._atlasIndexArray, z, indexForZ);
736         // update possible children
737         if (this._children) {
738             var locChildren = this._children;
739             for (var i = 0, len = locChildren.length; i < len; i++) {
740                 var child = locChildren[i];
741                 if (child) {
742                     var ai = child.getAtlasIndex();
743                     if (ai >= indexForZ)
744                         child.setAtlasIndex(ai + 1);
745                 }
746             }
747         }
748         this._tiles[z] = gid;
749         return tile;
750     },
751 
752     _updateTileForGID:function (gid, pos) {
753         var rect = this._tileSet.rectForGID(gid);
754         var locScaleFactor = this._contentScaleFactor;
755         rect = cc.rect(rect.x / locScaleFactor, rect.y / locScaleFactor,
756             rect.width / locScaleFactor, rect.height / locScaleFactor);
757         var z = pos.x + pos.y * this._layerSize.width;
758 
759         var tile = this._reusedTileWithRect(rect);
760         this._setupTileSprite(tile, pos, gid);
761 
762         // get atlas index
763         var indexForZ = this._atlasIndexForExistantZ(z);
764         tile.setAtlasIndex(indexForZ);
765         tile.setDirty(true);
766         tile.updateTransform();
767         this._tiles[z] = gid;
768 
769         return tile;
770     },
771 
772     //The layer recognizes some special properties, like cc_vertez
773     _parseInternalProperties:function () {
774         // if cc_vertex=automatic, then tiles will be rendered using vertexz
775         var vertexz = this.getProperty("cc_vertexz");
776         if (vertexz) {
777             if (vertexz == "automatic") {
778                 this._useAutomaticVertexZ = true;
779                 var alphaFuncVal = this.getProperty("cc_alpha_func");
780                 var alphaFuncValue = 0;
781                 if (alphaFuncVal)
782                     alphaFuncValue = parseFloat(alphaFuncVal);
783 
784                 if (cc.renderContextType === cc.WEBGL) {
785                     this.setShaderProgram(cc.ShaderCache.getInstance().programForKey(cc.SHADER_POSITION_TEXTURECOLORALPHATEST));
786                     var alphaValueLocation = cc.renderContext.getUniformLocation(this.getShaderProgram().getProgram(), cc.UNIFORM_ALPHA_TEST_VALUE_S);
787                     // NOTE: alpha test shader is hard-coded to use the equivalent of a glAlphaFunc(GL_GREATER) comparison
788                     this.getShaderProgram().use();
789                     this.getShaderProgram().setUniformLocationWith1f(alphaValueLocation, alphaFuncValue);
790                 }
791             } else
792                 this._vertexZvalue = parseInt(vertexz, 10);
793         }
794     },
795 
796     _setupTileSprite:function (sprite, pos, gid) {
797         var z = pos.x + pos.y * this._layerSize.width;
798         sprite.setPosition(this.getPositionAt(pos));
799         if (cc.renderContextType === cc.WEBGL)
800             sprite.setVertexZ(this._vertexZForPos(pos));
801         else
802             sprite.setTag(z);
803 
804         sprite.setAnchorPoint(cc.PointZero());
805         sprite.setOpacity(this._opacity);
806         if (cc.renderContextType === cc.WEBGL) {
807             sprite.setRotation(0.0);
808         }
809 
810         sprite.setFlippedX(false);
811         sprite.setFlippedY(false);
812 
813         // Rotation in tiled is achieved using 3 flipped states, flipping across the horizontal, vertical, and diagonal axes of the tiles.
814         if ((gid & cc.TMX_TILE_DIAGONAL_FLAG) >>> 0) {
815             // put the anchor in the middle for ease of rotation.
816             sprite.setAnchorPoint(cc.p(0.5, 0.5));
817             sprite.setPosition(this.getPositionAt(pos).x + sprite.getContentSize().height / 2,
818                 this.getPositionAt(pos).y + sprite.getContentSize().width / 2);
819 
820             var flag = (gid & (cc.TMX_TILE_HORIZONTAL_FLAG | cc.TMX_TILE_VERTICAL_FLAG) >>> 0) >>> 0;
821             // handle the 4 diagonally flipped states.
822             if (flag == cc.TMX_TILE_HORIZONTAL_FLAG)
823                 sprite.setRotation(90);
824             else if (flag == cc.TMX_TILE_VERTICAL_FLAG)
825                 sprite.setRotation(270);
826             else if (flag == (cc.TMX_TILE_VERTICAL_FLAG | cc.TMX_TILE_HORIZONTAL_FLAG) >>> 0) {
827                 sprite.setRotation(90);
828                 sprite.setFlippedX(true);
829             } else {
830                 sprite.setRotation(270);
831                 sprite.setFlippedX(true);
832             }
833         } else {
834             if ((gid & cc.TMX_TILE_HORIZONTAL_FLAG) >>> 0)
835                 sprite.setFlippedX(true);
836 
837             if ((gid & cc.TMX_TILE_VERTICAL_FLAG) >>> 0)
838                 sprite.setFlippedY(true);
839         }
840     },
841 
842     _reusedTileWithRect:function (rect) {
843         if(cc.renderContextType === cc.WEBGL){
844             if (!this._reusedTile) {
845                 this._reusedTile = new cc.Sprite();
846                 this._reusedTile.initWithTexture(this.getTexture(), rect, false);
847                 this._reusedTile.setBatchNode(this);
848             } else {
849                 // XXX HACK: Needed because if "batch node" is nil,
850                 // then the Sprite'squad will be reset
851                 this._reusedTile.setBatchNode(null);
852 
853                 // Re-init the sprite
854                 this._reusedTile.setTextureRect(rect, false, rect.size);
855 
856                 // restore the batch node
857                 this._reusedTile.setBatchNode(this);
858             }
859         } else {
860             this._reusedTile = new cc.Sprite();
861             this._reusedTile.initWithTexture(this._textureForCanvas, rect, false);
862             this._reusedTile.setBatchNode(this);
863             this._reusedTile.setParent(this);
864         }
865         return this._reusedTile;
866     },
867 
868     _vertexZForPos:function (pos) {
869         var ret = 0;
870         var maxVal = 0;
871         if (this._useAutomaticVertexZ) {
872             switch (this._layerOrientation) {
873                 case cc.TMX_ORIENTATION_ISO:
874                     maxVal = this._layerSize.width + this._layerSize.height;
875                     ret = -(maxVal - (pos.x + pos.y));
876                     break;
877                 case cc.TMX_ORIENTATION_ORTHO:
878                     ret = -(this._layerSize.height - pos.y);
879                     break;
880                 case cc.TMX_ORIENTATION_HEX:
881                     cc.log("TMX Hexa zOrder not supported");
882                     break;
883                 default:
884                     cc.log("TMX invalid value");
885                     break;
886             }
887         } else
888             ret = this._vertexZvalue;
889         return ret;
890     },
891 
892     _atlasIndexForExistantZ:function (z) {
893         var item;
894         if (this._atlasIndexArray) {
895             var locAtlasIndexArray = this._atlasIndexArray;
896             for (var i = 0, len = locAtlasIndexArray.length; i < len; i++) {
897                 item = locAtlasIndexArray[i];
898                 if (item == z)
899                     break;
900             }
901         }
902         if(!item)
903             cc.log("cc.TMXLayer._atlasIndexForExistantZ(): TMX atlas index not found. Shall not happen");
904         return i;
905     },
906 
907     _atlasIndexForNewZ:function (z) {
908         var locAtlasIndexArray = this._atlasIndexArray;
909         for (var i = 0, len = locAtlasIndexArray.length; i < len; i++) {
910             var val = locAtlasIndexArray[i];
911             if (z < val)
912                 break;
913         }
914         return i;
915     }
916 });
917 
918 if(cc.Browser.supportWebGL){
919     cc.TMXLayer.prototype.draw = cc.SpriteBatchNode.prototype.draw;
920 }else{
921     cc.TMXLayer.prototype.draw = cc.TMXLayer.prototype._drawForCanvas;
922 }
923 
924 /**
925  * Creates a cc.TMXLayer with an tile set info, a layer info and a map info
926  * @param {cc.TMXTilesetInfo} tilesetInfo
927  * @param {cc.TMXLayerInfo} layerInfo
928  * @param {cc.TMXMapInfo} mapInfo
929  * @return {cc.TMXLayer|Null}
930  */
931 cc.TMXLayer.create = function (tilesetInfo, layerInfo, mapInfo) {
932     var ret = new cc.TMXLayer();
933     if (ret.initWithTilesetInfo(tilesetInfo, layerInfo, mapInfo))
934         return ret;
935     return null;
936 };
937