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