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 }