1 /++
2 Module for loading maps in TMX format.
3 
4 > TMX and TSX are Tiled’s own formats for storing tile maps and tilesets, based on XML. 
5 > TMX provides a flexible way to describe a tile based map. It can describe maps with any tile size, 
6 > any amount of layers, any number of tile sets and it allows custom properties to be set on most elements. 
7 > Beside tile layers, it can also contain groups of objects that can be placed freely.
8 
9 To load and render tile maps, use the following:
10 ---
11 TimeMap tilemap = new TileMap();
12 tilemap.load("map.tmx");
13 ...
14 render.draw(tilemap, Vecf(32, 32)); // Draws the layers at the specified location 
15                                     // (including offset, of course).
16 ---
17 
18 $(HTTP https://doc.mapeditor.org/en/stable/reference/tmx-map-format/, TMX format documentation).
19 
20 Authors implemetation: $(HTTP https://github.com/TodNaz, TodNaz)
21 License: $(HTTP https://opensource.org/licenses/MIT, MIT)
22 +/
23 module tida.tiled;
24 
25 import dxml.parser;
26 import std.json;
27 import std.file : read;
28 import std.conv : to;
29 import std.base64;
30 
31 import tida.drawable;
32 import tida.render;
33 import tida.image;
34 import tida.color;
35 import tida.vector;
36 import tida.sprite;
37 
38 __gshared Tileset[] _tilesetStorage;
39 
40 ref Tileset[] tilesetStorage() @trusted
41 {
42     return _tilesetStorage;
43 }
44 
45 private T byteTo(T)(ubyte[] bytes) @trusted
46 {
47     T data = T.init;
48     foreach(i; 0 .. bytes.length) data |= bytes[i] << (8 * i);
49 
50     return data;
51 }
52 
53 private bool isKeyExists(JSONValue json, string key) @trusted {
54     try {
55         auto item = json[key];
56         return true;
57     }catch(Exception e) {
58         return false;
59     }
60 }
61 
62 /// Property included in layers.
63 struct Property
64 {
65     string name; /// Property name.
66     string type; /// The data type in the layer property.
67     string value; /// Property value.
68 }
69 
70 /// Object in ObjectGroup.
71 struct Object
72 {
73     int id; /// Unique idencificator
74     int gid; /// The identifier to the picture in the tile.
75     float x, y; /// Coordinates
76     float width, height; /// Size object.
77 }
78 
79 /// A group of objects.
80 class ObjectGroup
81 {
82     public
83     {
84         string name; /// Name objects group.
85         int id; /// Unique udencificator.
86         Color!ubyte color; /// Color.
87         Property[] properties; /// Object group properties.
88         Object[] objects; /// Objects.
89         bool visible = true; /// Is visible group
90         float   x, /// position
91                 y; /// ditto
92     }
93 }
94 
95 /// Map information structure
96 struct MapMeta
97 {
98     string ver; /// The version of the program in which the tile map was exported.
99     string tiledver; /// The version of the standard format for describing tile maps.
100     string orientation; /++ Tile map orientation (can be orthogonal, isometric 
101                             (the latter is not supported at the moment)) +/
102     string renderorder; /// Rendering method (right-to-left, etc.)
103     int compressionlevel; /// Compression level of these layers.
104     int width, height; /// Map size.
105     int tilewidth, tileheight; /// Tile unit size.
106     int hexsidelength; /++  Only for hexagonal maps. Determines the width or height 
107                             (depending on the staggered axis) of the tile’s edge, in pixels. +/
108     int staggeraxis; /// For staggered and hexagonal maps, determines which axis (“x” or “y”) is staggered.
109     Color!ubyte backgroundColor; /// The background color of the map
110     int nexlayerid; /// Stores the next available ID for new layers.
111     int nextobjectid; /// Stores the next available ID for new objects.
112     bool infinite; /// Whether this map is infinite.
113 }
114 
115 /// Tileset information.
116 struct TilesetMeta
117 {
118     string ver; /// The version of the program in which the tile map was exported.
119     string tiledver; /// The version of the standard format for describing tile maps.
120     string name; /// Name tileset.
121     int tilewidth, tileheight; /// Tile unit size.
122     int tilecount; /// Tile count.
123     int columns; /// How many lines the map for tiles is split into.
124     int spacing; /// 
125     string objectalignment; ///
126     string imgsource; /// The path to the file image.
127 }
128 
129 /// 
130 class Tileset
131 {
132     public 
133     {
134         int firstgid; /// First identificator.
135         string source; /// Path to the tileset description file.
136 
137         TilesetMeta meta; /// Tileset information.
138         string imagesource; /// Path to the image file.
139 
140         Image image; /// Atlas.
141         Image[] data; /// Tiles.
142         bool isSetup = false;
143     }
144 
145     /// Prepares the atlas for a group of tiles.
146     void setup() @safe
147     {
148         image = new Image().load(imagesource);
149         //foreach (i; 0 .. meta.columns)
150         //{
151         //    data ~= image.strip(0, i * meta.tileheight, meta.tilewidth, meta.tileheight);
152         //}
153 
154         foreach (i; 0 .. meta.columns)
155         {
156             data ~= image.strip(0, i * meta.tileheight, meta.tilewidth, meta.tileheight);
157         }
158 
159         foreach (e; data) e.toTexture();
160 
161         isSetup = true;
162     }
163 
164     void parse(R, int Type)(R element)
165     {
166         static if (Type == MapType.XML)
167         {
168             foreach (attrib; element.attributes)
169             {
170                 if(attrib.name == "source") {
171                     imagesource = attrib.value;
172                 }
173             }
174         }
175     }
176 
177     /// Loading tileset information from a file.
178     void load() @trusted
179     {
180         auto xml = parseXML(cast(string) read(source));
181 
182         foreach(element; xml) {
183             if(	element.type == EntityType.elementStart ||
184                 element.type == EntityType.elementEmpty)
185             {
186                 if(element.name == "tileset")
187                 {
188                     foreach(attrib; element.attributes) {
189                         if(attrib.name == "version") meta.ver = attrib.value;
190                         if(attrib.name == "tiledversion") meta.tiledver = attrib.value;
191                         if(attrib.name == "name") meta.name = attrib.value;
192                         if(attrib.name == "tilewidth") meta.tilewidth = attrib.value.to!int;
193                         if(attrib.name == "tileheight") meta.tileheight = attrib.value.to!int;
194                         if(attrib.name == "tilecount") meta.tilecount = attrib.value.to!int;
195                         if(attrib.name == "columns") meta.columns = attrib.value.to!int;
196                     }
197                 }
198 
199                 if(element.name == "image")
200                 {
201                     foreach(attrib; element.attributes) {
202                         if(attrib.name == "source") {
203                             imagesource = attrib.value;
204                         }
205                     }
206                 }
207             }
208         }
209     }
210 }
211 
212 /// The data of the tiles in the layer.
213 struct LayerData
214 {
215     import std.array, std.string, std.numeric;
216 
217     string encoding; /// Encoding type.
218     string compression; /// Compression type.
219     uint[] datamap; /// Data of the tiles in the layer.
220 
221     /++
222         Reads data from an input stream with both XML and JSON data.
223 
224         Params:
225             R = Type data.
226             Type = What card format was provided for reading (XML or JSON)
227             data = Layer data.
228             compressionLevel = Compression level.
229     +/
230     void parse(R, int Type)(R data, int compressionlevel = -1) @trusted
231     {
232         static if(Type == MapType.XML)
233         {
234             if(data.type == EntityType.elementStart)
235             {
236                 foreach(attrib; data.attributes) {
237                     if(attrib.name == "encoding") encoding = attrib.value;
238                     if(attrib.name == "compression") compression = attrib.value;
239                 }
240             }else
241             if(data.type == EntityType.text)
242             {
243                 if(encoding == "csv")
244                 {
245                     string csvdata = data.text;
246                     string[] stolbs = csvdata.split('\n');
247                     foreach(es; stolbs) {
248                         foreach(ei; es.split(',')) {
249                             if(ei.isNumeric)
250                                 datamap ~= ei.to!int;
251                         }
252                     }
253                 }else
254                 if(encoding == "base64")
255                 {
256                     import std.zlib;
257                     import zstd;
258                     import std.encoding;
259 
260                     ubyte[] decoded = Base64.decode(data.text.strip);
261 
262                     if(compression == "zlib")
263                     {
264                         decoded = cast(ubyte[]) std.zlib.uncompress(cast(void[]) decoded);
265                     }else
266                     if(compression == "gzip")
267                     {
268                         decoded = cast(ubyte[]) (new std.zlib.UnCompress(HeaderFormat.gzip).uncompress(cast(void[]) decoded));
269                     }else
270                     if(compression == "zstd")
271                     {
272                         auto unc = new zstd.Decompressor();
273                         ubyte[] swapData;
274             
275                         immutable chunkLen = decoded.length / compressionlevel;
276 
277                         for(int i = 0; i < decoded.length / chunkLen; i++)
278                         {
279                             swapData ~= unc.decompress(decoded[(i * chunkLen) .. ((i + 1) * chunkLen)]); 
280                         }
281 
282                         decoded = swapData;
283                     }
284 
285                     for(int i = 0; i < decoded.length; i += 4)
286                     {
287                         datamap ~= decoded[i .. i + 4].byteTo!uint;
288                     }
289                 }
290             }
291         }else
292         static if(Type == MapType.JSON) 
293         {
294             if(data["encoding"].str == "csv")
295             {
296                 string csvdata = data["data"].str;
297                 string[] stolbs = csvdata.split('\n');
298                 foreach(es; stolbs) {
299                     foreach(ei; es.split(',')) {
300                         if(ei.isNumeric)
301                             datamap ~= ei.to!int;
302                     }
303                 }
304             }else
305             if(data["encoding"].str == "base64")
306             {
307                 import std.zlib;
308                 import zstd;
309                 import std.encoding;
310 
311                 ubyte[] decoded = Base64.decode(data["data"].str);
312 
313                 if(compression == "zlib")
314                 {
315                     decoded = cast(ubyte[]) std.zlib.uncompress(cast(void[]) decoded);
316                 }else
317                 if(compression == "gzip")
318                 {
319                     decoded = cast(ubyte[]) (new std.zlib.UnCompress(HeaderFormat.gzip).uncompress(cast(void[]) decoded));
320                 }else
321                 if(compression == "zstd")
322                 {
323                     auto unc = new zstd.Decompressor();
324                     ubyte[] swapData;
325 
326                     immutable chunkLen = decoded.length / compressionlevel;
327 
328                     for(int i = 0; i < decoded.length / chunkLen; i++)
329                     {
330                         swapData ~= unc.decompress(decoded[(i * chunkLen) .. ((i + 1) * chunkLen)]); 
331                     }
332 
333                     decoded = swapData;
334                 }
335 
336                 for(int i = 0; i < decoded.length; i += 4)
337                 {
338                     datamap ~= decoded[i .. i + 4].byteTo!int;
339                 }
340             }
341         }
342     }
343 }
344 
345 /// Map format
346 enum MapType : int
347 {
348     XML, JSON
349 }
350 
351 /// A layer from a group of tiles.
352 class TileLayer : IDrawable
353 {
354     public
355     {
356         int id; /// Unique identificator.
357         string name; /// Layer data.
358         int width, /// Layer size.
359             height; /// Layer size.
360         int offsetx, /// Layer offset.
361             offsety; /// Layer offset.
362         LayerData data; /// Layer data.
363         Property[] properties; /// Layer properties.
364         bool visible = true; /// Is visible layer.
365         Color!ubyte transparentcolor; /// Parent color.
366     }
367 
368     protected
369     {
370         TileMap tilemap;
371         Sprite[] sprites;
372     }
373 
374     ///
375     this(TileMap tilemap) @safe
376     {
377         this.tilemap = tilemap;
378     }
379 
380     /// Prepares tiles into a sprite group for lightweight rendering.
381     void setup() @safe {
382         int y = 0;
383         Sprite currSprite = null;
384         for(int i = 0; i < this.data.datamap.length; i++) {
385             currSprite = new Sprite();
386             if((i - (y * this.width)) == this.width) y++;
387             immutable index = this.data.datamap[i] - 1;
388             if(index != -1)
389             {
390                 currSprite.draws = tilemap.tile(index);
391                 currSprite.position = Vecf((i - (y * this.width)) * tilemap.mapinfo.tilewidth,
392                                             y * tilemap.mapinfo.tileheight) + Vecf(this.offsetx, this.offsety);
393                 sprites ~= currSprite;
394             }
395 
396             currSprite = null;
397         }
398     }
399 
400     override void draw(IRenderer render, Vecf position) @safe
401     {
402         foreach(e; sprites)
403             render.draw(e, position);
404     }
405 }
406 
407 /// A layer from a picture.
408 class ImageLayer : IDrawable
409 {
410     public
411     {
412         int id; /// Unique identificator.
413         string name; /// Layer name
414         float   offsetx, /// Layer offset. (position) 
415                 offsety; /// Layer offset. (position)
416         Image image; /// Layer picture.
417         string imagesource; /// Path to the file.
418         int x, /// x 
419             y; /// y
420         float opacity = 1.0f; /// Opacity.
421         bool visible = true; /// Layer is visible?
422         Color!ubyte tintcolor; /// Tint color
423         Property[] properties; /// Layer properties.
424     }
425 
426     /// Reading data from a document element.
427     void parse(R, string nameof)(R data) @safe
428     {
429         static if(nameof == "imagelayer")
430         {
431             foreach(attrib; data.attributes)
432             {
433                 if(attrib.name == "id") id = attrib.value.to!int;
434                 if(attrib.name == "name") name = attrib.value;
435                 if(attrib.name == "offsetx") offsetx = attrib.value.to!float;
436                 if(attrib.name == "offsety") offsety = attrib.value.to!float;
437                 if(attrib.name == "opacity") opacity = attrib.value.to!float;
438                 if(attrib.name == "visible") visible = attrib.value.to!int == 1;
439                 if(attrib.name == "tintcolor") tintcolor = Color!ubyte(attrib.value);
440             }
441         }else
442         static if(nameof == "image")
443         {
444             image = new Image();
445             foreach(attrib; data.attributes) {
446                 if(attrib.name == "source") imagesource = attrib.value;
447             }
448 
449             image
450                 .load(imagesource)
451                 .toTexture();
452         }
453     }
454 
455     override void draw(IRenderer render, Vecf position) @safe
456     {
457         render.draw(image, Vecf(offsetx, offsety));
458     }
459 }
460 
461 /// Tile Map.
462 class TileMap : IDrawable
463 {
464     public
465     {
466         MapMeta mapinfo; /// Map information.
467         Tileset[] tilesets; /// Tileset's.
468         TileLayer[] layers; /// Layer's.
469         ObjectGroup[] objgroups; /// Object group's.
470         ImageLayer[] imagelayers; /// Image layer's.
471     }
472 
473     private
474     {
475         Image _opt_back;
476     }
477 
478     void bindExistsFrom(Tileset[] tilesets)
479     {
480         this.tilesets ~= tilesets; 
481     }
482 
483     /++
484         Loading data from memory.
485 
486         Params:
487             data = Data (XML/JSON).
488     +/
489     void loadFromMem(R, int Type)(R data) @trusted
490     {
491         static if(Type == MapType.XML)
492         {
493             auto xml = parseXML(data);
494 
495             TileLayer currentLayer = null;
496             ObjectGroup currentGroup = null;
497             ImageLayer currentImage = null;
498             Tileset tset = null;
499             
500             bool isprop = false;
501             bool dataelement = false;
502             bool isimage = false;
503             bool istset = false;
504 
505             foreach(element; xml)
506             {
507                 if(	element.type == EntityType.elementStart ||
508                     element.type == EntityType.elementEmpty)
509                 {
510                     if(element.name == "map") {
511                         foreach(attr; element.attributes)
512                         {
513                             if(attr.name == "version") mapinfo.ver = attr.value;
514                             if(attr.name == "tiledversion") mapinfo.tiledver = attr.value;
515                             if(attr.name == "orientation") mapinfo.orientation = attr.value;
516                             if(attr.name == "renderorder") mapinfo.renderorder = attr.value;
517                             if(attr.name == "width") mapinfo.width = attr.value.to!int;
518                             if(attr.name == "height") mapinfo.height = attr.value.to!int;
519                             if(attr.name == "tilewidth") mapinfo.tilewidth = attr.value.to!int;
520                             if(attr.name == "tileheight") mapinfo.tileheight = attr.value.to!int;
521                             if(attr.name == "infinite") mapinfo.infinite = attr.value.to!int == 1;
522                             if(attr.name == "backgroundcolor") mapinfo.backgroundColor = Color!ubyte(attr.value);
523                             if(attr.name == "nextlayerid") mapinfo.nexlayerid = attr.value.to!int;
524                             if(attr.name == "nextobjectid") mapinfo.nextobjectid = attr.value.to!int;
525                             if(attr.name == "compressionlevel") mapinfo.compressionlevel = attr.value.to!int;
526                         }
527                     }else
528                     if(element.name == "tileset") {
529                         Tileset temp = new Tileset();
530 
531                         foreach(attr; element.attributes)
532                         {
533                             if(attr.name == "firstgid") temp.firstgid = attr.value.to!int;
534                             if(attr.name == "source") temp.source = attr.value;
535                             if(attr.name == "tilewidth") temp.meta.tilewidth = attr.value.to!int;
536                             if(attr.name == "tileheight") temp.meta.tileheight = attr.value.to!int;
537                             if(attr.name == "tilecount") temp.meta.tilecount = attr.value.to!int;
538                             if(attr.name == "columns") temp.meta.columns = attr.value.to!int;
539                         }
540 
541                         if (temp.source != "")
542                         {
543                             temp.load();
544                         } else
545                         {
546                             istset = true;
547                             tset = temp;
548                         }
549 
550                         bool needLoad = true;
551                         foreach (e; tilesetStorage)
552                         {
553                             if (e.source == temp.source)
554                             {
555                                 needLoad = false;
556                                 tilesets ~= e;
557                                 break;
558                             }
559                         }
560                
561 
562                         if (needLoad)
563                         {
564                             tilesets ~= temp;
565                         } else
566                         {
567                             destroy(temp);
568                             tset = null;
569                             istset = false;
570                         }
571                     }else
572                     if(element.name == "layer")
573                     {
574                         TileLayer layer = new TileLayer(this);
575 
576                         foreach(attrib; element.attributes)
577                         {
578                             if(attrib.name == "name") layer.name = attrib.value;
579                             if(attrib.name == "id") layer.id = attrib.value.to!int;
580                             if(attrib.name == "width") layer.width = attrib.value.to!int;
581                             if(attrib.name == "height") layer.height = attrib.value.to!int;
582                             if(attrib.name == "offsetx") layer.offsetx = attrib.value.to!int;
583                             if(attrib.name == "offsety") layer.offsety = attrib.value.to!int;
584                         }
585 
586                         currentLayer = layer;
587                     }else
588                     if(element.name == "data") {
589                         currentLayer.data.parse!(typeof(element), MapType.XML)(element);
590                         dataelement = true;
591                     }else
592                     if(element.name == "objectgroup") {
593                         ObjectGroup temp = new ObjectGroup();
594                         foreach(attrib; element.attributes) {
595                             if(attrib.name == "color") temp.color = Color!ubyte(attrib.value);
596                             if(attrib.name == "id") temp.id = attrib.value.to!int;
597                             if(attrib.name == "name") temp.name = attrib.value;
598                         }
599                         currentGroup = temp;
600                     }else
601                     if(element.name == "properties") {
602                         isprop = true;
603                     }else
604                     if(element.name == "property") {
605                         Property prop;
606                         foreach(attrib; element.attributes) {
607                             if(attrib.name == "name") prop.name = attrib.value;
608                             if(attrib.name == "type") prop.type = attrib.value;
609                             if(attrib.name == "value") prop.value = attrib.value;
610                         }
611 
612                         if(currentGroup !is null) currentGroup.properties ~= prop; else 
613                         if(currentLayer !is null) currentLayer.properties ~= prop; else
614                         if(currentImage !is null) currentImage.properties ~= prop;
615                     }else
616                     if(element.name == "object") {
617                         Object obj;
618                         foreach(attrib; element.attributes) {
619                             if(attrib.name == "id") obj.id = attrib.value.to!int;
620                             if(attrib.name == "gid") obj.gid = attrib.value.to!int;
621                             if(attrib.name == "x") obj.x = attrib.value.to!float;
622                             if(attrib.name == "y") obj.y = attrib.value.to!float;
623                             if(attrib.name == "width") obj.width = attrib.value.to!float;
624                             if(attrib.name == "height") obj.height = attrib.value.to!float;
625                         }
626 
627                         currentGroup.objects ~= obj;
628                     }else
629                     if(element.name == "imagelayer") {
630                         currentImage = new ImageLayer();
631                         currentImage.parse!(typeof(element), "imagelayer")(element);
632                     }else
633                     if(element.name == "image") {
634                         isimage = true;
635                         if(currentImage !is null) currentImage.parse!(typeof(element), "image")(element);
636                         else
637                             if(tset !is null) tset.parse!(typeof(element), MapType.XML)(element);
638                     }
639                 }else
640                 if(element.type == EntityType.elementEnd) {
641                     if(element.name == "layer") {
642                         layers ~= currentLayer;
643                         currentLayer = null;
644                     }else
645                     if(element.name == "data") {
646                         dataelement = false;
647                     }else
648                     if(element.name == "objectgroup") {
649                         objgroups ~= currentGroup;
650                         currentGroup = null;
651                     }else
652                     if(element.name == "properties") {
653                         isprop = false;
654                     }else
655                     if(element.name == "imagelayer") {
656                         imagelayers ~= currentImage;
657                         currentImage = null;
658                     }else
659                     if(element.name == "image") {
660                         isimage = false;
661                     }else
662                     if(element.name == "tileset") {
663                         tset = null;
664                         istset = false;
665                     }
666                 }else
667                 if(element.type == EntityType.text) {
668                     if(dataelement) {
669                         currentLayer.data.parse!(typeof(element),Type)(element, mapinfo.compressionlevel);
670                     }else
671                     if(isimage) {
672                         if(currentImage !is null)
673                             currentImage.parse!(typeof(element),"image")(element);
674                     }
675                 }
676             }
677         }else
678         static if(Type == MapType.JSON)
679         {
680             auto json = data.parseJSON;
681 
682             Property[] propertiesParse(JSONValue elem) @trusted
683             {
684                 Property[] prs;
685                 foreach(e; elem["properties"].array) {
686                     Property temp;
687                     temp.name = e["name"].str;
688                     temp.type = e["type"].str;
689                     if(temp.type == "int") 
690                         temp.value = e["value"].get!int.to!string;
691                     else
692                     if(temp.type == "string")
693                         temp.value = e["value"].str;
694 
695                     prs ~= temp;
696                 }
697 
698                 return prs;
699             }
700 
701             assert(json.isKeyExists("type"), "It's not a json map!");
702             assert(json["type"].str == "map", "It's not a json map!");
703 
704             if(json.isKeyExists("backgroundcolor")) mapinfo.backgroundColor = Color!ubyte(json["backgroundcolor"].str);
705             mapinfo.compressionlevel = json["compressionlevel"].get!int;
706             mapinfo.width = json["width"].get!int;
707             mapinfo.height = json["height"].get!int;
708             mapinfo.infinite = json["infinite"].get!bool;
709             mapinfo.nexlayerid = json["nextlayerid"].get!int;
710             if(json.isKeyExists("nextobjectid")) mapinfo.nextobjectid = json["nextobjectid"].get!int;
711             mapinfo.orientation = json["orientation"].str;
712             mapinfo.tiledver = json["tiledversion"].str;
713             mapinfo.ver = json["version"].str;
714             mapinfo.tilewidth = json["tilewidth"].get!int;
715             mapinfo.tileheight = json["tileheight"].get!int;
716             mapinfo.renderorder = json["renderorder"].str;
717 
718             foreach(e; json["tilesets"].array) {
719                 Tileset tileset = new Tileset();
720                 tileset.firstgid = e["firstgid"].get!int;
721                 tileset.source = e["source"].str;
722                 tileset.load();
723                 tilesets ~= tileset;
724             }
725 
726             foreach(e; json["layers"].array) {
727                 if(e["type"].str == "tilelayer") {
728                     TileLayer tilelayer = new TileLayer(this);
729                     tilelayer.id = e["id"].get!int;
730                     tilelayer.visible = e["visible"].get!bool;
731                     if(e.isKeyExists("offsetx")) tilelayer.offsetx = e["offsetx"].get!int;
732                     if(e.isKeyExists("offsety")) tilelayer.offsety = e["offsety"].get!int;
733                     tilelayer.width = e["width"].get!int;
734                     tilelayer.height = e["height"].get!int;
735                     tilelayer.data.compression = e["compression"].str;
736                     tilelayer.data.encoding = e["encoding"].str;
737                     if(e.isKeyExists("transparentcolor")) 
738                         tilelayer.transparentcolor = Color!ubyte(e["transparentcolor"].str);
739                     tilelayer.data.parse!(typeof(e),Type)(e, mapinfo.compressionlevel);
740                     if(e.isKeyExists("properties"))
741                         tilelayer.properties = propertiesParse(e["properties"]);
742                     layers ~= tilelayer;
743                 }else
744                 if(e["type"].str == "imagelayer") {
745                     ImageLayer imagelayer = new ImageLayer();
746                     imagelayer.id = e["id"].get!int;
747                     imagelayer.name = e["name"].str;
748                     imagelayer.imagesource = e["image"].str;
749                     imagelayer.opacity = e["opacity"].get!float;
750                     imagelayer.visible = e["visible"].get!bool;
751                     imagelayer.offsetx = e["offsetx"].get!int;
752                     imagelayer.offsety = e["offsety"].get!int;
753                     if(e.isKeyExists("properties")) imagelayer.properties = propertiesParse(e);
754                     imagelayer.image = new Image().load(imagelayer.imagesource);
755                     imagelayer.image.toTexture();
756                     imagelayers ~= imagelayer;
757                 }else
758                 if(e["type"].str == "objectgroup") {
759                     ObjectGroup group = new ObjectGroup();
760                     group.id = e["id"].get!int;
761                     group.name = e["name"].str;
762                     group.color = Color!ubyte(e["color"].str);
763                     group.visible = e["visible"].get!bool;
764                     if(e.isKeyExists("offsetx")) group.x = e["offsetx"].get!int;
765                     if(e.isKeyExists("offsety")) group.y = e["offsety"].get!int;
766                     foreach(elem; e["objects"].array) {
767                         Object obj;
768                         obj.gid = elem["gid"].get!int;
769                         obj.width = elem["width"].get!int;
770                         obj.height = elem["height"].get!int;
771                         obj.x = elem["x"].get!int;
772                         obj.y = elem["y"].get!int;
773                         group.objects ~= obj;
774                     }
775                     if(e.isKeyExists("properties")) group.properties = propertiesParse(e);
776                 }
777             }
778         }
779     }
780 
781     /// Load data from file.
782     void load(string path) @trusted
783     {
784         import std.path;
785 
786         if(path.extension == ".tmx")
787         {
788             loadFromMem!(string, MapType.XML)(cast(string) read(path));
789         }else
790         if(path.extension == ".json")
791         {
792             loadFromMem!(string, MapType.JSON)(cast(string) read(path));
793         }
794     }
795 
796     public
797     {
798         IDrawable[] drawableSort;
799     }
800 
801     void sort() @safe
802     {
803         import std.algorithm : sort;
804 
805         drawableSort = [];
806 
807         struct SortLayerStruct { IDrawable obj; int id; }
808         SortLayerStruct[] list;
809         foreach(e; layers) list ~= SortLayerStruct(e, e.id);
810         foreach(e; imagelayers) list ~= SortLayerStruct(e, e.id);
811 
812         sort!((a,b) => a.id < b.id)(list);
813 
814         foreach(e; list) drawableSort ~= e.obj;
815     }
816 
817     /// Prepare layers for work.
818     void setup() @trusted
819     {
820         import std.algorithm : canFind;
821 
822         foreach(e; tilesets)
823         {
824             if (!tilesetStorage.canFind(e))
825                 tilesetStorage ~= e;
826 
827             if (!e.isSetup)
828                 e.setup();
829         }
830 
831         foreach(e; layers)
832         {
833             e.setup();
834         }
835 
836         sort();
837     }
838 
839     ///
840     ObjectGroup objgroupByName(string name) @safe
841     {
842         foreach(e; objgroups) if(e.name == name) return e;
843 
844         return ObjectGroup.init;
845     }
846 
847     ///
848     Image tile(int id) @safe
849     {
850         Image image = null;
851         int currTileSet = 0;
852         int countPrevious = 0;
853 
854         while (image is null)
855         {
856             if(currTileSet == this.tilesets.length) break;
857 
858             countPrevious += this.tilesets[currTileSet].data.length;
859 
860             if(id > countPrevious) {
861                 currTileSet++;
862             }else
863                 image = this.tilesets[currTileSet].data[currTileSet != 0 ? id - countPrevious : id];
864         }
865 
866         return image;
867     }
868 
869     void optimize() @safe
870     {
871         import tida.softimage;
872         import tida.shape;
873         import tida.game : renderer;
874         import std.algorithm : remove;
875 
876         Color!ubyte previous = renderer.background;
877         renderer.background = rgba(0, 0, 0, 0);
878         renderer.clear();
879 
880         foreach (e; drawableSort)
881         {
882             if ((cast(TileLayer) e) !is null)
883             {
884                 renderer.clear();
885                 renderer.draw(e, vecf(0, 0));
886                 layers = layers.remove!(a => a is e);
887 
888                 TileLayer lobj = cast(TileLayer) e;
889 
890                 renderer.draw(lobj, vecf(0,0));
891                 _opt_back = renderRead( renderer, vecf(0, 0), mapinfo.width * mapinfo.tilewidth, 
892                                         mapinfo.height * mapinfo.tileheight);
893 
894                 _opt_back = _opt_back.flip!(YAxis);
895                 _opt_back.toTexture;
896 
897                 ImageLayer imglayer = new ImageLayer();
898                 imglayer.offsetx = lobj.offsetx;
899                 imglayer.offsety = lobj.offsety;
900                 imglayer.image = _opt_back;
901                 imglayer.id = lobj.id;
902                 imagelayers ~= imglayer;
903 
904                 _opt_back = null;
905             }
906         }
907 
908         sort();
909 
910         renderer.background = previous;
911         renderer.clear();
912     }
913 
914     override void draw(IRenderer render, Vecf position) @safe
915     {
916         foreach(e; drawableSort)
917         {
918             render.draw(e, position);
919         }
920     }
921 }