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