Просмотр исходного кода

encoder now supports converting *.tmx to *.scene.
Added support for "enabled" in SceneLoader.
"Reverted" issue with loading child nodes for SpriteSample.

Rcmaniac25 11 лет назад
Родитель
Сommit
13f2253023

+ 9 - 1
gameplay/src/SceneLoader.cpp

@@ -99,7 +99,8 @@ Scene* SceneLoader::loadInternal(const char* url)
         SceneNodeProperty::SCRIPT |
         SceneNodeProperty::SPRITE |
         SceneNodeProperty::TILESET |
-        SceneNodeProperty::TEXT);
+        SceneNodeProperty::TEXT |
+        SceneNodeProperty::ENABLED);
     applyNodeProperties(sceneProperties, SceneNodeProperty::COLLISION_OBJECT);
 
     // Apply node tags
@@ -439,6 +440,9 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
         case SceneNodeProperty::SCRIPT:
             node->addScript(snp._value.c_str());
             break;
+        case SceneNodeProperty::ENABLED:
+            node->setEnabled(snp._value.compare("true") == 0);
+            break;
         default:
             GP_ERROR("Unsupported node property type (%d).", snp._type);
             break;
@@ -852,6 +856,10 @@ void SceneLoader::parseNode(Properties* ns, SceneNode* parent, const std::string
         {
             addSceneNodeProperty(sceneNode, SceneNodeProperty::SCRIPT, ns->getString());
         }
+        else if (strcmp(name, "enabled") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::ENABLED, ns->getString());
+        }
         else
         {
             GP_ERROR("Unsupported node property: %s = %s", name, ns->getString());

+ 2 - 1
gameplay/src/SceneLoader.h

@@ -61,7 +61,8 @@ private:
             SCRIPT = 2048,
             SPRITE = 4096,
             TILESET = 8192,
-            TEXT = 16384
+            TEXT = 16384,
+            ENABLED = 32768
         };
 
         SceneNodeProperty(Type type, const std::string& value, int index, bool isUrl);

+ 12 - 12
samples/browser/res/common/sprites/sprite.scene

@@ -139,6 +139,18 @@ scene spriteSample
 
 	node player
 	{
+		node text
+		{
+			text
+			{
+				font = res/ui/arial.gpb
+
+				text = P1
+				size = 18
+				color = 0, 0, 1, 1
+			}
+		}
+
 		sprite
 		{
 			path = res/common/sprites/player1.png
@@ -176,18 +188,6 @@ scene spriteSample
 		translate = 0, -50, 0
 	}
 
-	node text
-	{
-		text
-		{
-			font = res/ui/arial.gpb
-
-			text = P1
-			size = 18
-			color = 0, 0, 1, 1
-		}
-	}
-
 	// Set active camera
 	activeCamera = camera
 }

+ 6 - 11
samples/browser/src/SpriteSample.cpp

@@ -50,17 +50,12 @@ void SpriteSample::initialize()
     _playerAnimation->getClip("walk")->setSpeed(24.0f/1000.0f);
     _playerAnimation->play("idle");
 
-    // Setup player text
-    Node* playerTextNode = _scene->findNode("text");
-    playerTextNode->addRef();
-    _scene->removeNode(playerTextNode); //XXX This is because SceneLoader doesn't support loading child nodes for other nodes
-    _playerNode->addChild(playerTextNode);
-
-    playerTextNode->translateY(_playerSprite->getHeight());
-    Text* playerText = dynamic_cast<Text*>(playerTextNode->getDrawable());
-    playerText->setJustify(Font::ALIGN_TOP_HCENTER);
-    playerText->setWidth(_playerSprite->getWidth());
-    SAFE_RELEASE(playerTextNode);
+	// Setup player text
+	Node* playerTextNode = _playerNode->findNode("text");
+	playerTextNode->translateY(_playerSprite->getHeight());
+	Text* playerText = dynamic_cast<Text*>(playerTextNode->getDrawable());
+	playerText->setJustify(Font::ALIGN_TOP_HCENTER);
+	playerText->setWidth(_playerSprite->getWidth());
 
     // Custom Effect in sprite
     Effect* waterEffect = Effect::createFromFile("res/shaders/sprite.vert", "res/common/sprites/water2d.frag");

+ 4 - 0
tools/encoder/gameplay-encoder.vcxproj

@@ -14,6 +14,7 @@
     <ClCompile Include="src\Animation.cpp" />
     <ClCompile Include="src\AnimationChannel.cpp" />
     <ClCompile Include="src\Base.cpp" />
+    <ClCompile Include="src\Base64.cpp" />
     <ClCompile Include="src\BoundingVolume.cpp" />
     <ClCompile Include="src\Camera.cpp" />
     <ClCompile Include="src\Constants.cpp" />
@@ -51,6 +52,7 @@
     <ClCompile Include="src\Scene.cpp" />
     <ClCompile Include="src\StringUtil.cpp" />
     <ClCompile Include="src\TMXSceneEncoder.cpp" />
+    <ClCompile Include="src\TMXTypes.cpp" />
     <ClCompile Include="src\Transform.cpp" />
     <ClCompile Include="src\TTFFontEncoder.cpp" />
     <ClCompile Include="src\Vector2.cpp" />
@@ -63,6 +65,7 @@
     <ClInclude Include="src\Animation.h" />
     <ClInclude Include="src\AnimationChannel.h" />
     <ClInclude Include="src\Base.h" />
+    <ClInclude Include="src\Base64.h" />
     <ClInclude Include="src\BoundingVolume.h" />
     <ClInclude Include="src\Camera.h" />
     <ClInclude Include="src\Constants.h" />
@@ -100,6 +103,7 @@
     <ClInclude Include="src\StringUtil.h" />
     <ClInclude Include="src\Thread.h" />
     <ClInclude Include="src\TMXSceneEncoder.h" />
+    <ClInclude Include="src\TMXTypes.h" />
     <ClInclude Include="src\Transform.h" />
     <ClInclude Include="src\TTFFontEncoder.h" />
     <ClInclude Include="src\Vector2.h" />

+ 12 - 0
tools/encoder/gameplay-encoder.vcxproj.filters

@@ -142,6 +142,12 @@
     <ClCompile Include="src\TMXSceneEncoder.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\TMXTypes.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Base64.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\VertexElement.h">
@@ -285,6 +291,12 @@
     <ClInclude Include="src\TMXSceneEncoder.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\TMXTypes.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Base64.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\Vector2.inl">

+ 28 - 1
tools/encoder/src/EncoderArguments.cpp

@@ -28,7 +28,8 @@ EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _textOutput(false),
     _optimizeAnimations(false),
     _animationGrouping(ANIMATIONGROUP_PROMPT),
-    _outputMaterial(false)
+    _outputMaterial(false),
+    _generateTextureGutter(false)
 {
     __instance = this;
 
@@ -86,6 +87,12 @@ const char* EncoderArguments::getFilePathPointer() const
     return _filePath.c_str();
 }
 
+const std::string EncoderArguments::getFileName() const
+{
+    int pos = _filePath.find_last_of('/');
+    return (pos == -1 ? _filePath : _filePath.substr(pos + 1));
+}
+
 std::string EncoderArguments::getOutputDirPath() const
 {
     if (_fileOutputPath.size() > 0)
@@ -104,6 +111,8 @@ std::string EncoderArguments::getOutputFileExtension() const
 {
     switch (getFileFormat())
     {
+    case FILEFORMAT_TMX:
+        return ".scene";
     case FILEFORMAT_PNG:
     case FILEFORMAT_RAW:
         if (_normalMap)
@@ -271,6 +280,11 @@ void EncoderArguments::printUsage() const
         "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
         "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
     "\n" \
+    "TMX file options:\n" \
+    "  -tg\tEnable texture gutter's around tiles. This will modify any referenced\n" \
+    "  \ttile sets to add a 1px border around it to prevent seams.\n"
+    "  -tg:none\tDo not priduce a texture gutter.\n"
+    "\n" \
     "Normal map options:\n" \
         "  -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
         "  -s\t\tSize/resolution of the input heightmap image (required for RAW files)\n" \
@@ -319,6 +333,11 @@ bool EncoderArguments::outputMaterialEnabled() const
     return _outputMaterial;
 }
 
+bool EncoderArguments::generateTextureGutter() const
+{
+    return _generateTextureGutter;
+}
+
 const char* EncoderArguments::getNodeId() const
 {
     if (_nodeId.length() == 0)
@@ -656,6 +675,14 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
                 _tangentBinormalId.insert(nodeId);
             }
         }
+        else if (str.compare("-textureGutter:none") == 0 || str.compare("-tg:none") == 0)
+        {
+            _generateTextureGutter = false;
+        }
+        else if (str.compare("-textureGutter") == 0 || str.compare("-tg") == 0)
+        {
+            _generateTextureGutter = true;
+        }
         break;
     case 'v':
         (*index)++;

+ 8 - 0
tools/encoder/src/EncoderArguments.h

@@ -80,6 +80,11 @@ public:
      */
     const char* getFilePathPointer() const;
 
+    /**
+     * Get the file name of the input file.
+     */
+    const std::string getFileName() const;
+
     /**
      * Returns the output path/folder.
      * Example: "C:/dir"
@@ -165,6 +170,8 @@ public:
 
     bool outputMaterialEnabled() const;
 
+    bool generateTextureGutter() const;
+
     const char* getNodeId() const;
 
     static std::string getRealPath(const std::string& filepath);
@@ -211,6 +218,7 @@ private:
     bool _optimizeAnimations;
     AnimationGroupOption _animationGrouping;
     bool _outputMaterial;
+    bool _generateTextureGutter;
 
     std::vector<std::string> _groupAnimationNodeId;
     std::vector<std::string> _groupAnimationAnimationId;

+ 624 - 11
tools/encoder/src/TMXSceneEncoder.cpp

@@ -1,12 +1,28 @@
 #include <string>
 
+#include <zlib.h>
+
+#include "Base64.h"
 #include "TMXSceneEncoder.h"
 
 using namespace gameplay;
 using namespace tinyxml2;
 using std::string;
 
-TMXSceneEncoder::TMXSceneEncoder()
+#ifdef GP_4_SPACE_TABS
+#define TAB_STRING(count) string((count) * 4, ' ')
+#else
+#define TAB_STRING(count) string((count), '\t')
+#endif
+
+#define BUFFER_SIZE 256
+
+#ifdef WIN32
+#define snprintf(s, n, fmt, ...) sprintf((s), (fmt), __VA_ARGS__)
+#endif
+
+TMXSceneEncoder::TMXSceneEncoder() :
+    _tabCount(0)
 {
 }
 
@@ -14,16 +30,613 @@ TMXSceneEncoder::~TMXSceneEncoder()
 {
 }
 
-void TMXSceneEncoder::write(const string& filepath)
+void TMXSceneEncoder::write(const string& filepath, const EncoderArguments& arguments)
+{
+    XMLDocument xmlDoc;
+    XMLError err;
+    if ((err = xmlDoc.LoadFile(filepath.c_str())) != XML_NO_ERROR)
+    {
+        LOG(1, "Call to XMLDocument::LoadFile() failed.\n");
+        LOG(1, "Error returned: %d\n\n", err);
+        return;
+    }
+    
+    // Parse the Tiled map
+    string outputFilePath = arguments.getOutputFilePath();
+    int pos = outputFilePath.find_last_of('/');
+    string outputDirectory = (pos == -1 ? outputFilePath : outputFilePath.substr(0, pos));
+
+    TMXMap map;
+    if (!parseTmx(xmlDoc, map, outputDirectory))
+    {
+        return;
+    }
+
+    //XXX arguments.generateTextureGutter()
+
+    // Write the tile map
+    string fileName = arguments.getFileName();
+    pos = fileName.find_last_of('.');
+
+    writeScene(map, outputFilePath, (pos == -1 ? fileName : fileName.substr(0, pos)));
+}
+
+bool TMXSceneEncoder::parseTmx(const XMLDocument& xmlDoc, TMXMap& map, const std::string& outputDirectory) const
+{
+    auto xmlMap = xmlDoc.FirstChildElement("map");
+    if (!xmlMap)
+    {
+        LOG(1, "Missing root <map> element.\n");
+        return false;
+    }
+
+    // Read in the map values //XXX should compact this so XML attribute parsing is a little nicer
+    unsigned int uiValue;
+    auto attValue = xmlMap->Attribute("width");
+    sscanf(attValue, "%u", &uiValue);
+    map.setWidth(uiValue);
+
+    attValue = xmlMap->Attribute("height");
+    sscanf(attValue, "%u", &uiValue);
+    map.setHeight(uiValue);
+
+    float fValue;
+    attValue = xmlMap->Attribute("tilewidth");
+    sscanf(attValue, "%f", &fValue);
+    map.setTileWidth(fValue);
+
+    attValue = xmlMap->Attribute("tileheight");
+    sscanf(attValue, "%f", &fValue);
+    map.setTileHeight(fValue);
+
+    // Now we load all tilesets
+    auto xmlTileSet = xmlMap->FirstChildElement("tileset");
+    while (xmlTileSet)
+    {
+        TMXTileSet tileSet;
+
+        attValue = xmlTileSet->Attribute("firstgid");
+        sscanf(attValue, "%u", &uiValue);
+        tileSet.setFirstGid(uiValue);
+
+        XMLDocument sourceXmlDoc;
+        const XMLElement* xmlTileSetToLoad;
+        attValue = xmlTileSet->Attribute("source");
+        if (attValue)
+        {
+            string tsxLocation = outputDirectory + "/" + attValue;
+            tsxLocation = EncoderArguments::getRealPath(tsxLocation);
+
+            XMLError err;
+            if ((err = sourceXmlDoc.LoadFile(tsxLocation.c_str())) != XML_NO_ERROR)
+            {
+                LOG(1, "Could not load tileset's source TSX.\n");
+                return false;
+            }
+            xmlTileSetToLoad = sourceXmlDoc.RootElement();
+        }
+        else
+        {
+            xmlTileSetToLoad = xmlTileSet;
+        }
+
+        // Maximum tile size
+        attValue = xmlTileSetToLoad->Attribute("tilewidth");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            tileSet.setMaxTileWidth(uiValue);
+        }
+        else
+        {
+            tileSet.setMaxTileWidth(map.getTileWidth());
+        }
+        attValue = xmlTileSetToLoad->Attribute("tileheight");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            tileSet.setMaxTileHeight(uiValue);
+        }
+        else
+        {
+            tileSet.setMaxTileHeight(map.getTileHeight());
+        }
+
+        // Spacing and margin
+        attValue = xmlTileSetToLoad->Attribute("spacing");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            tileSet.setSpacing(uiValue);
+        }
+        attValue = xmlTileSetToLoad->Attribute("margin");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            tileSet.setMargin(uiValue);
+        }
+
+        // Tile offset
+        auto xmlTileOffset = xmlTileSetToLoad->FirstChildElement("tileoffset");
+        if (xmlTileOffset)
+        {
+            Vector2 offset;
+            int iValue;
+
+            attValue = xmlTileOffset->Attribute("x");
+            sscanf(attValue, "%d", &iValue);
+            offset.x = iValue;
+            attValue = xmlTileOffset->Attribute("y");
+            sscanf(attValue, "%d", &iValue);
+            offset.y = iValue;
+
+            tileSet.setOffset(offset);
+        }
+
+        // Load image source. Don't worry about <data>, trans, or height
+        auto xmlTileSetImage = xmlTileSetToLoad->FirstChildElement("image");
+        tileSet.setImagePath(xmlTileSetImage->Attribute("source"));
+
+        // We only care about image width...
+        attValue = xmlTileSetImage->Attribute("width");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            tileSet.setImageWidth(uiValue);
+        }
+        else
+        {
+            // Load the image itself to get the width
+            string imageLocation = outputDirectory + "/" + tileSet.getImagePath();
+            imageLocation = EncoderArguments::getRealPath(imageLocation);
+
+            int pos = imageLocation.find_last_of('.');
+            if (imageLocation.substr(pos).compare(".png") != 0)
+            {
+                LOG(1, "TileSet image must be a PNG. %s\n", imageLocation.c_str());
+                return false;
+            }
+
+            Image* img = Image::create(imageLocation.c_str());
+            if (!img)
+            {
+                LOG(1, "Could not load TileSet image. %s\n", imageLocation.c_str());
+                return false;
+            }
+            tileSet.setImageWidth(img->getWidth());
+            SAFE_DELETE(img);
+        }
+
+        //TODO: tiles (specifically, tile animations)
+        //<tile id="relId"><animation><frame tileid="relId" duration="numInMS"></...></...></...>
+
+        // Save the tileset
+        map.addTileSet(tileSet);
+
+        xmlTileSet = xmlTileSet->NextSiblingElement("tileset");
+    }
+
+    // Finally we load the layers
+    auto xmlLayer = xmlMap->FirstChildElement("layer");
+    while (xmlLayer)
+    {
+        TMXLayer layer;
+
+        //XXX what if name doesn't exist?
+        layer.setName(xmlLayer->Attribute("name"));
+
+        // Load properties
+        attValue = xmlLayer->Attribute("width");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            layer.setWidth(uiValue);
+        }
+        else
+        {
+            layer.setWidth(map.getWidth());
+        }
+
+        attValue = xmlLayer->Attribute("height");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            layer.setHeight(uiValue);
+        }
+        else
+        {
+            layer.setHeight(map.getHeight());
+        }
+
+        attValue = xmlLayer->Attribute("opacity");
+        if (attValue)
+        {
+            sscanf(attValue, "%f", &fValue);
+            layer.setOpacity(fValue);
+        }
+
+        attValue = xmlLayer->Attribute("visible");
+        if (attValue)
+        {
+            sscanf(attValue, "%u", &uiValue);
+            layer.setVisible(uiValue == 1);
+        }
+
+        // Load tiles
+        layer.setupTiles();
+        auto data = loadDataElement(xmlLayer->FirstChildElement("data"));
+        auto data_size = data.size();
+        for (int i = 0; i < data_size; i++)
+        {
+            //XXX this might depend on map's renderorder
+            auto x = i % layer.getWidth();
+            auto y = i / layer.getWidth();
+            layer.setTile(x, y, data[i]);
+        }
+
+        // Save layer
+        map.addLayer(layer);
+
+        xmlLayer = xmlLayer->NextSiblingElement("layer");
+    }
+
+    //TODO: imagelayer (a kind of individual sprite)
+
+    return true;
+}
+
+//XXX could probably seperate the writing process to a seperate class (PropertyWriter...)
+#define WRITE_PROPERTY_BLOCK_START(str) writeLine(file, (str)); \
+    writeLine(file, "{"); \
+    _tabCount++
+#define WRITE_PROPERTY_BLOCK_END() _tabCount--; \
+    writeLine(file, "}")
+#define WRITE_PROPERTY_BLOCK_VALUE(name, value) writeLine(file, string(name) + " = " + (value))
+#define WRITE_PROPERTY_DIRECT(value) writeLine(file, (value))
+#define WRITE_PROPERTY_NEWLINE() file << std::endl
+
+void TMXSceneEncoder::writeScene(const TMXMap& map, const string& outputFilepath, const string& sceneName)
+{
+    // Prepare for writing the scene
+    std::ofstream file(outputFilepath.c_str(), std::ofstream::out | std::ofstream::trunc);
+
+    auto tilesetCount = map.getLayerCount();
+    bool singleLayer = tilesetCount <= 1;
+
+    //XXX should really build an ordered list, and draw based on that (for z-depth)
+
+    // Write initial scene
+    WRITE_PROPERTY_BLOCK_START("scene " + sceneName);
+    if (!singleLayer)
+    {
+        WRITE_PROPERTY_BLOCK_START("node all_tilesets"); //XXX what if there are sprites between tilesets?
+    }
+
+    // Write all tilesets
+    for (auto i = 0u; i < tilesetCount; i++)
+    {
+        writeTileset(map, i, file);
+        WRITE_PROPERTY_NEWLINE();
+    }
+
+    if (!singleLayer)
+    {
+        WRITE_PROPERTY_BLOCK_END(); // node tilesets
+    }
+
+    //TODO: other sprites
+
+    WRITE_PROPERTY_BLOCK_END(); // scene
+
+    // Cleanup
+    file.flush();
+    file.close();
+}
+
+// This is actually a misnomer. What is a Layer in Tiled/TMX is a TileSet for GamePlay3d. TileSet in Tiled/TMX is something different.
+void TMXSceneEncoder::writeTileset(const TMXMap& map, unsigned int tileSetIndex, std::ofstream& file)
+{
+    auto tileset = map.getLayer(tileSetIndex);
+    if (!tileset.hasTiles())
+    {
+        return;
+    }
+
+    auto tilesets = tileset.getTilesetsUsed(map);
+    if (tilesets.size() == 0)
+    {
+        return;
+    }
+
+    char buffer[BUFFER_SIZE];
+    WRITE_PROPERTY_BLOCK_START("node " + tileset.getName());
+    if (tilesets.size() > 1)
+    {
+        auto i = 0u;
+        for (auto it = tilesets.begin(); it != tilesets.end(); it++, i++)
+        {
+            snprintf(buffer, BUFFER_SIZE, "node tileset_%d", i);
+            WRITE_PROPERTY_BLOCK_START(buffer);
+
+            auto tmxTileset = map.getTileSet(*it);
+            writeSoloTileset(map, tmxTileset, tileset, file, *it);
+
+            auto tileOffset = tmxTileset.getOffset();
+            if (!(tileOffset == Vector2::zero()))
+            {
+                // Tile offset moves the tiles, not the origin of each tile
+                snprintf(buffer, BUFFER_SIZE, "translate = %d, %d, 0", static_cast<int>(tileOffset.x), static_cast<int>(tileOffset.y));
+                WRITE_PROPERTY_NEWLINE();
+                WRITE_PROPERTY_DIRECT(buffer);
+            }
+
+            WRITE_PROPERTY_BLOCK_END();
+            if ((i + 1) != tilesets.size())
+            {
+                WRITE_PROPERTY_NEWLINE();
+            }
+        }
+    }
+    else
+    {
+        auto tmxTileset = map.getTileSet(*(tilesets.begin()));
+        writeSoloTileset(map, tmxTileset, tileset, file);
+
+        auto tileOffset = tmxTileset.getOffset();
+        if (!(tileOffset == Vector2::zero()))
+        {
+            // Tile offset moves the tiles, not the origin of each tile
+            snprintf(buffer, BUFFER_SIZE, "translate = %d, %d, 0", static_cast<int>(tileOffset.x), static_cast<int>(tileOffset.y));
+            WRITE_PROPERTY_NEWLINE();
+            WRITE_PROPERTY_DIRECT(buffer);
+        }
+    }
+    if (!tileset.getVisible())
+    {
+        // Default is true, so only write it false
+        WRITE_PROPERTY_NEWLINE();
+        WRITE_PROPERTY_DIRECT("enabled = false");
+    }
+    WRITE_PROPERTY_BLOCK_END();
+}
+
+void TMXSceneEncoder::writeSoloTileset(const TMXMap& map, const gameplay::TMXTileSet& tmxTileset, const TMXLayer& tileset, std::ofstream& file, unsigned int resultOnlyForTileset)
+{
+    WRITE_PROPERTY_BLOCK_START("tileset");
+
+    // Write tile path
+    WRITE_PROPERTY_BLOCK_VALUE("path", tmxTileset.getImagePath());
+    WRITE_PROPERTY_NEWLINE();
+
+    // Write tile size
+    //XXX if tile sizes are incorrect, make sure to update TMXLayer::getTileStart too
+    char buffer[BUFFER_SIZE];
+    snprintf(buffer, BUFFER_SIZE, "tileWidth = %u", tmxTileset.getMaxTileWidth());
+    WRITE_PROPERTY_DIRECT(buffer);
+    snprintf(buffer, BUFFER_SIZE, "tileHeight = %u", tmxTileset.getMaxTileHeight());
+    WRITE_PROPERTY_DIRECT(buffer);
+
+    // Write tileset size
+    snprintf(buffer, BUFFER_SIZE, "columns = %u", tileset.getWidth());
+    WRITE_PROPERTY_DIRECT(buffer);
+    snprintf(buffer, BUFFER_SIZE, "rows = %u", tileset.getHeight());
+    WRITE_PROPERTY_DIRECT(buffer);
+    WRITE_PROPERTY_NEWLINE();
+
+    // Write opacity
+    if (tileset.getOpacity() < 1.0f)
+    {
+        snprintf(buffer, BUFFER_SIZE, "opacity = %f", tileset.getOpacity());
+        WRITE_PROPERTY_DIRECT(buffer);
+        WRITE_PROPERTY_NEWLINE();
+    }
+
+    // Write tiles
+    auto tileset_height = tileset.getHeight();
+    auto tileset_width = tileset.getWidth();
+    for (auto y = 0u; y < tileset_height; y++)
+    {
+        bool tilesWritten = false;
+        for (auto x = 0u; x < tileset_width; x++)
+        {
+            Vector2 startPos = tileset.getTileStart(x, y, map, resultOnlyForTileset);
+            if (startPos.x < 0 || startPos.y < 0)
+            {
+                continue;
+            }
+
+            tilesWritten = true;
+            WRITE_PROPERTY_BLOCK_START("tile");
+            snprintf(buffer, BUFFER_SIZE, "cell = %u, %u", x, y);
+            WRITE_PROPERTY_DIRECT(buffer);
+            snprintf(buffer, BUFFER_SIZE, "source = %u, %u", static_cast<unsigned int>(startPos.x), static_cast<unsigned int>(startPos.y));
+            WRITE_PROPERTY_DIRECT(buffer);
+            WRITE_PROPERTY_BLOCK_END();
+        }
+        if (tilesWritten && ((y + 1) != tileset_height))
+        {
+            WRITE_PROPERTY_NEWLINE();
+        }
+    }
+
+    WRITE_PROPERTY_BLOCK_END();
+}
+
+#undef WRITE_PROPERTY_NEWLINE
+#undef WRITE_PROPERTY_DIRECT
+#undef WRITE_PROPERTY_BLOCK_VALUE
+#undef WRITE_PROPERTY_BLOCK_END
+#undef WRITE_PROPERTY_BLOCK_START
+
+void TMXSceneEncoder::writeLine(std::ofstream& file, const string& line) const
+{
+    file << TAB_STRING(_tabCount) << line << std::endl;
+}
+
+bool isBase64(char c)
+{
+    return (c >= 'a' && c <= 'z') ||
+        (c >= 'A' && c <= 'Z') ||
+        (c >= '0' && c <= '9') ||
+        (c == '=');
+}
+
+std::vector<unsigned int> TMXSceneEncoder::loadDataElement(const XMLElement* data)
 {
-	XMLDocument xmlDoc;
-	XMLError err;
-	if ((err = xmlDoc.LoadFile(filepath.c_str())) != XML_NO_ERROR)
-	{
-		LOG(1, "Call to XMLDocument::LoadFile() failed.\n");
-		LOG(1, "Error returned: %d\n\n", err);
-		exit(-1);
-	}
+    if (!data)
+    {
+        return std::vector<unsigned int>();
+    }
+
+    const char* encoding = data->Attribute("encoding");
+    const char* compression = data->Attribute("compression");
+
+    const char* attValue = "0";
+    unsigned int tileGid;
+    std::vector<unsigned int> tileData;
+
+    if (!compression && !encoding)
+    {
+        auto xmlTile = data->FirstChildElement("tile");
+        while (xmlTile)
+        {
+            attValue = xmlTile->Attribute("gid");
+            sscanf(attValue, "%u", &tileGid);
+            tileData.push_back(tileGid);
+
+            xmlTile = xmlTile->NextSiblingElement("tile");
+        }
+    }
+    else
+    {
+        if (strcmp(encoding, "csv") == 0)
+        {
+            if (compression)
+            {
+                LOG(1, "Compression is not supported with CSV encoding.\n");
+                exit(-1);
+            }
+
+            auto rawCsvData = data->GetText();
+            int start = 0;
+            char* endptr;
+            // Skip everything before values
+            while (rawCsvData[start] == ' ' || rawCsvData[start] == '\n' || rawCsvData[start] == '\r' || rawCsvData[start] == '\t')
+            {
+                start++;
+            }
+            // Iterate through values. Skipping to next value when done
+            while (rawCsvData[start])
+            {
+                tileData.push_back(strtoul(rawCsvData + start, &endptr, 10));
+                start = endptr - rawCsvData;
+                while (rawCsvData[start] == ' ' || rawCsvData[start] == ',' || rawCsvData[start] == '\n' || rawCsvData[start] == '\r' || rawCsvData[start] == '\t')
+                {
+                    start++;
+                }
+            }
+        }
+        else if (strcmp(encoding, "base64") == 0)
+        {
+            string base64Data = data->GetText();
+            if (base64Data.size() > 0 && (!isBase64(base64Data[0]) || !isBase64(base64Data[base64Data.size() - 1])))
+            {
+                int start = 0;
+                auto end = base64Data.size() - 1;
+                while (!isBase64(base64Data[start]))
+                {
+                    start++;
+                }
+                while (!isBase64(base64Data[end]))
+                {
+                    end--;
+                }
+                base64Data = base64Data.substr(start, end - start + 1);
+            }
+            auto byteData = base64_decode(base64Data);
+
+            if (compression)
+            {
+                int err;
+                const unsigned int buffer_size = 4096;
+                char buffer[buffer_size];
+                string decomData;
+
+                if (strcmp(compression, "zlib") == 0 || strcmp(compression, "gzip") == 0)
+                {
+                    z_stream d_stream;
+                    d_stream.zalloc = Z_NULL;
+                    d_stream.zfree = Z_NULL;
+                    d_stream.opaque = Z_NULL;
+                    d_stream.next_in = (Bytef*)byteData.c_str();
+                    d_stream.avail_in = byteData.size();
+
+                    if (strcmp(compression, "zlib") == 0)
+                    {
+                        // zlib
+                        if ((err = inflateInit(&d_stream)) != Z_OK)
+                        {
+                            LOG(1, "ZLIB inflateInit failed. Error: %d.\n", err);
+                            exit(-1);
+                        }
+                    }
+                    else
+                    {
+                        // gzip
+                        if ((err = inflateInit2(&d_stream, 16+MAX_WBITS)) != Z_OK)
+                        {
+                            LOG(1, "ZLIB inflateInit2 failed. Error: %d.\n", err);
+                            exit(-1);
+                        }
+                    }
+
+                    do
+                    {
+                        d_stream.next_out = (Bytef*)buffer;
+                        d_stream.avail_out = buffer_size;
+                        err = inflate(&d_stream, Z_NO_FLUSH);
+                        switch (err)
+                        {
+                            case Z_NEED_DICT:
+                            case Z_DATA_ERROR:
+                            case Z_MEM_ERROR:
+                                inflateEnd(&d_stream);
+                                LOG(1, "ZLIB inflate failed. Error: %d.\n", err);
+                                exit(-1);
+                                break;
+                        }
+
+                        string decomBlock(buffer, buffer_size - d_stream.avail_out);
+                        decomData += decomBlock;
+                    } while (err != Z_STREAM_END);
+
+                    inflateEnd(&d_stream);
+                }
+                else
+                {
+                    LOG(1, "Unknown compression: %s.\n", compression);
+                    exit(-1);
+                }
+
+                byteData = decomData;
+            }
+
+            auto byteDataSize = byteData.size();
+            for (unsigned int i = 0; i < byteDataSize; i += 4)
+            {
+                unsigned int gid = static_cast<unsigned char>(byteData[i + 0]) |
+                    (static_cast<unsigned char>(byteData[i + 1]) << 8u) | 
+                    (static_cast<unsigned char>(byteData[i + 2]) << 16u) |
+                    (static_cast<unsigned char>(byteData[i + 3]) << 24u);
+                tileData.push_back(gid);
+            }
+        }
+        else
+        {
+            LOG(1, "Unknown encoding: %s.\n", encoding);
+            exit(-1);
+        }
+    }
 
-	//TODO
+    return tileData;
 }

+ 32 - 15
tools/encoder/src/TMXSceneEncoder.h

@@ -1,10 +1,14 @@
 #ifndef TMXSCENEEENCODER_H_
 #define TMXSCENEEENCODER_H_
 
+#include <fstream>
 #include <tinyxml2.h>
 
 #include "Base.h"
-#include "StringUtil.h"
+#include "Vector2.h"
+#include "TMXTypes.h"
+#include "EncoderArguments.h"
+#include "Image.h"
 
 /**
  * Class for encoding an TMX file.
@@ -12,20 +16,33 @@
 class TMXSceneEncoder
 {
 public:
-	/**
-	* Constructor.
-	*/
-	TMXSceneEncoder();
-
-	/**
-	* Destructor.
-	*/
-	~TMXSceneEncoder();
-
-	/**
-	* Writes out encoded FBX file.
-	*/
-	void write(const std::string& filepath);
+    /**
+     * Constructor.
+     */
+    TMXSceneEncoder();
+
+    /**
+     * Destructor.
+     */
+    ~TMXSceneEncoder();
+
+    /**
+     * Writes out encoded TMX file.
+     */
+    void write(const std::string& filepath, const gameplay::EncoderArguments& arguments);
+
+private:
+    static std::vector<unsigned int> loadDataElement(const tinyxml2::XMLElement* data);
+
+    bool parseTmx(const tinyxml2::XMLDocument& xmlDoc, gameplay::TMXMap& map, const std::string& outputDirectory) const;
+    void writeScene(const gameplay::TMXMap& map, const std::string& outputFilepath, const std::string& sceneName);
+
+    void writeTileset(const gameplay::TMXMap& map, unsigned int tileSetIndex, std::ofstream& file);
+    void writeSoloTileset(const gameplay::TMXMap& map, const gameplay::TMXTileSet& tmxTileset, const gameplay::TMXLayer& tileset, std::ofstream& file, unsigned int resultOnlyForTileset = TMX_INVALID_ID);
+
+    void writeLine(std::ofstream& file, const std::string& line) const;
+
+    unsigned int _tabCount;
 };
 
 #endif

+ 1 - 1
tools/encoder/src/main.cpp

@@ -116,7 +116,7 @@ int main(int argc, const char** argv)
         {
             std::string realpath(arguments.getFilePath());
             TMXSceneEncoder tmxEncoder;
-            tmxEncoder.write(realpath);
+            tmxEncoder.write(realpath, arguments);
             break;
         }
     case EncoderArguments::FILEFORMAT_TTF: