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