Explorar o código

Merge pull request #962 from dgough/next

Updated the encoder to generate a material file (experimental)
Sean Paul Taylor %!s(int64=12) %!d(string=hai) anos
pai
achega
19338261fa
Modificáronse 40 ficheiros con 2551 adicións e 1055 borrados
  1. 1 0
      gameplay/src/AudioBuffer.cpp
  2. 4 4
      gameplay/src/AudioSource.cpp
  3. 32 2
      gameplay/src/Bundle.cpp
  4. 8 0
      gameplay/src/Bundle.h
  5. 44 0
      gameplay/src/FileSystem.cpp
  6. 17 0
      gameplay/src/FileSystem.h
  7. 3 3
      gameplay/src/ImageControl.cpp
  8. 3 3
      gameplay/src/Material.cpp
  9. 3 3
      gameplay/src/ParticleEmitter.cpp
  10. 74 6
      gameplay/src/Properties.cpp
  11. 27 4
      gameplay/src/Properties.h
  12. 4 2
      gameplay/src/SceneLoader.cpp
  13. 25 17
      gameplay/src/Terrain.cpp
  14. 6 0
      tools/encoder/CMakeLists.txt
  15. 8 0
      tools/encoder/gameplay-encoder.vcxproj
  16. 24 0
      tools/encoder/gameplay-encoder.vcxproj.filters
  17. 31 0
      tools/encoder/src/Constants.cpp
  18. 33 0
      tools/encoder/src/Constants.h
  19. 14 1
      tools/encoder/src/EncoderArguments.cpp
  20. 2 0
      tools/encoder/src/EncoderArguments.h
  21. 458 973
      tools/encoder/src/FBXSceneEncoder.cpp
  22. 79 0
      tools/encoder/src/FBXSceneEncoder.h
  23. 809 0
      tools/encoder/src/FBXUtil.cpp
  24. 206 0
      tools/encoder/src/FBXUtil.h
  25. 8 0
      tools/encoder/src/FileIO.cpp
  26. 5 0
      tools/encoder/src/FileIO.h
  27. 5 0
      tools/encoder/src/GPBFile.cpp
  28. 5 0
      tools/encoder/src/GPBFile.h
  29. 325 16
      tools/encoder/src/Material.cpp
  30. 74 11
      tools/encoder/src/Material.h
  31. 11 1
      tools/encoder/src/Mesh.cpp
  32. 3 0
      tools/encoder/src/Mesh.h
  33. 5 0
      tools/encoder/src/MeshSkin.cpp
  34. 2 0
      tools/encoder/src/MeshSkin.h
  35. 55 5
      tools/encoder/src/Model.cpp
  36. 3 1
      tools/encoder/src/Model.h
  37. 1 1
      tools/encoder/src/Object.h
  38. 82 0
      tools/encoder/src/Sampler.cpp
  39. 50 0
      tools/encoder/src/Sampler.h
  40. 2 2
      tools/encoder/src/TTFFontEncoder.cpp

+ 1 - 0
gameplay/src/AudioBuffer.cpp

@@ -327,6 +327,7 @@ bool AudioBuffer::loadWav(Stream* stream, ALuint buffer)
             }
         }
     }
+    return false;
 }
 
 bool AudioBuffer::loadOgg(Stream* stream, ALuint buffer)

+ 4 - 4
gameplay/src/AudioSource.cpp

@@ -77,18 +77,18 @@ AudioSource* AudioSource::create(Properties* properties)
         return NULL;
     }
 
-    const char* path = properties->getString("path");
-    if (path == NULL)
+    std::string path;
+    if (!properties->getPath("path", &path))
     {
         GP_ERROR("Audio file failed to load; the file path was not specified.");
         return NULL;
     }
 
     // Create the audio source.
-    AudioSource* audio = AudioSource::create(path);
+    AudioSource* audio = AudioSource::create(path.c_str());
     if (audio == NULL)
     {
-        GP_ERROR("Audio file '%s' failed to load properly.", path);
+        GP_ERROR("Audio file '%s' failed to load properly.", path.c_str());
         return NULL;
     }
 

+ 32 - 2
gameplay/src/Bundle.cpp

@@ -286,6 +286,24 @@ const char* Bundle::getIdFromOffset(unsigned int offset) const
     return NULL;
 }
 
+const std::string& Bundle::getMaterialPath()
+{
+    if (_materialPath.empty())
+    {
+        int pos = _path.find_last_of('.');
+        if (pos > 2)
+        {
+            _materialPath = _path.substr(0, pos);
+            _materialPath.append(".material");
+            if (!FileSystem::fileExists(_materialPath.c_str()))
+            {
+                _materialPath.clear();
+            }
+        }
+    }
+    return _materialPath;
+}
+
 Bundle::Reference* Bundle::seekTo(const char* id, unsigned int type)
 {
     Reference* ref = find(id);
@@ -968,8 +986,20 @@ Model* Bundle::readModel(const char* nodeId)
             }
             if (materialCount > 0)
             {
-                // TODO: Material loading not supported yet.
-                GP_WARN("Material loading is not yet supported.");
+                for (unsigned int i = 0; i < materialCount; ++i)
+                {
+                    std::string materialName = readString(_stream);
+                    std::string materialPath = getMaterialPath();
+                    materialPath.append("#");
+                    materialPath.append(materialName);
+                    Material* material = Material::create(materialPath.c_str());
+                    if (material)
+                    {
+                        int partIndex = model->getMesh()->getPartCount() > 0 ? i : -1;
+                        model->setMaterial(material, partIndex);
+                        SAFE_RELEASE(material);
+                    }
+                }
             }
             return model;
         }

+ 8 - 0
gameplay/src/Bundle.h

@@ -189,6 +189,13 @@ private:
      */
     const char* getIdFromOffset(unsigned int offset) const;
 
+    /**
+     * Gets the path to the bundle's default material file, if it exists.
+     * 
+     * @return The bundle's default material path. Returns an empty string if the default material does not exist.
+     */
+    const std::string& getMaterialPath();
+
     /**
      * Seeks the file pointer to the object with the given ID and type
      * and returns the relevant Reference.
@@ -427,6 +434,7 @@ private:
     bool skipNode();
 
     std::string _path;
+    std::string _materialPath;
     unsigned int _referenceCount;
     Reference* _references;
     Stream* _stream;

+ 44 - 0
gameplay/src/FileSystem.cpp

@@ -10,9 +10,12 @@
     #include <windows.h>
     #include <tchar.h>
     #include <stdio.h>
+    #include <direct.h>
     #define gp_stat _stat
     #define gp_stat_struct struct stat
 #else
+    #define __EXT_POSIX2
+    #include <libgen.h>
     #include <dirent.h>
     #define gp_stat stat
     #define gp_stat_struct struct stat
@@ -560,6 +563,47 @@ void FileSystem::createFileFromAsset(const char* path)
 #endif
 }
 
+std::string FileSystem::dirname(const char* path)
+{
+    if (path == NULL || strlen(path) == 0)
+    {
+        return "";
+    }
+#ifdef WIN32
+    char drive[_MAX_DRIVE];
+    char dir[_MAX_DIR];
+    _splitpath(path, drive, dir, NULL, NULL);
+    std::string dirname;
+    size_t driveLength = strlen(drive);
+    if (driveLength > 0)
+    {
+        dirname.reserve(driveLength + strlen(dir));
+        dirname.append(drive);
+        dirname.append(dir);
+    }
+    else
+    {
+        dirname.assign(dir);
+    }
+    std::replace(dirname.begin(), dirname.end(), '\\', '/');
+    return dirname;
+#else
+    // dirname() modifies the input string so create a temp string
+    std::string dirname;
+    char* tempPath = new char[strlen(path)];
+    strcpy(tempPath, path);
+    char* dir = ::dirname(tempPath);
+    if (dir && strlen(dir) > 0)
+    {
+        dirname.assign(dir);
+        // dirname() strips off the trailing '/' so add it back to be consistent with Windows
+        dirname.append("/");
+    }
+    SAFE_DELETE_ARRAY(tempPath);
+    return dirname;
+#endif
+}
+
 std::string FileSystem::getExtension(const char* path)
 {
     const char* str = strrchr(path, '.');

+ 17 - 0
gameplay/src/FileSystem.h

@@ -2,6 +2,7 @@
 #define FILESYSTEM_H_
 
 #include "Stream.h"
+#include <string>
 
 namespace gameplay
 {
@@ -181,6 +182,22 @@ public:
      */
     static void createFileFromAsset(const char* path);
 
+    /**
+     * Returns the directory name up to and including the trailing '/'.
+     * 
+     * This is a lexical method so it does not verify that the directory exists.
+     * Back slashes will be converted to forward slashes.
+     * 
+     * - "res/image.png" will return "res/"
+     * - "image.png" will return ""
+     * - "c:\foo\bar\image.png" will return "c:/foo/bar/"
+     * 
+     * @param The file path. May be relative or absolute, forward or back slashes. May be NULL.
+     * 
+     * @return The directory name with the trailing '/'. Returns "" if path is NULL or the path does not contain a directory.
+     */
+    static std::string dirname(const char* path);
+
     /**
      * Returns the extension of the given file path.
      *

+ 3 - 3
gameplay/src/ImageControl.cpp

@@ -47,10 +47,10 @@ void ImageControl::initialize(Theme::Style* style, Properties* properties)
 
     Control::initialize(style, properties);
 
-    const char* path = properties->getString("path");
-    if (path)
+    std::string path;
+    if (properties->getPath("path", &path))
     {
-        setImage(path);
+        setImage(path.c_str());
     }
 
     if (properties->exists("srcRegion"))

+ 3 - 3
gameplay/src/Material.cpp

@@ -412,8 +412,8 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
             }
 
             // Get the texture path.
-            const char* path = ns->getString("path");
-            if (path == NULL || strlen(path) == 0)
+            std::string path;
+            if (!ns->getPath("path", &path))
             {
                 GP_ERROR("Texture sampler '%s' is missing required image file path.", name);
                 continue;
@@ -428,7 +428,7 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
 
             // Set the sampler parameter.
             GP_ASSERT(renderState->getParameter(name));
-            Texture::Sampler* sampler = renderState->getParameter(name)->setValue(path, mipmap);
+            Texture::Sampler* sampler = renderState->getParameter(name)->setValue(path.c_str(), mipmap);
             if (sampler)
             {
                 sampler->setWrapMode(wrapS, wrapT);

+ 3 - 3
gameplay/src/ParticleEmitter.cpp

@@ -111,8 +111,8 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
 
     // Load sprite properties.
     // Path to image file is required.
-    const char* texturePath = sprite->getString("path");
-    if (!texturePath || strlen(texturePath) == 0)
+    std::string texturePath;
+    if (!sprite->getPath("path", &texturePath))
     {
         GP_ERROR("Failed to load particle emitter: required image file path ('path') is missing.");
         return NULL;
@@ -184,7 +184,7 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
     bool orbitAcceleration = properties->getBool("orbitAcceleration");
 
     // Apply all properties to a newly created ParticleEmitter.
-    ParticleEmitter* emitter = ParticleEmitter::create(texturePath, textureBlending, particleCountMax);
+    ParticleEmitter* emitter = ParticleEmitter::create(texturePath.c_str(), textureBlending, particleCountMax);
     if (!emitter)
     {
         GP_ERROR("Failed to create particle emitter.");

+ 74 - 6
gameplay/src/Properties.cpp

@@ -26,12 +26,14 @@ void calculateNamespacePath(const std::string& urlString, std::string& fileStrin
 Properties* getPropertiesFromNamespacePath(Properties* properties, const std::vector<std::string>& namespacePath);
 
 Properties::Properties()
+    : _dirPath(NULL), _parent(NULL)
 {
 }
 
 Properties::Properties(const Properties& copy)
-    : _namespace(copy._namespace), _id(copy._id), _parentID(copy._parentID), _properties(copy._properties)
+    : _namespace(copy._namespace), _id(copy._id), _parentID(copy._parentID), _properties(copy._properties), _dirPath(NULL), _parent(copy._parent)
 {
+    setDirectoryPath(copy._dirPath);
     _namespaces = std::vector<Properties*>();
     std::vector<Properties*>::const_iterator it;
     for (it = copy._namespaces.begin(); it < copy._namespaces.end(); ++it)
@@ -44,12 +46,14 @@ Properties::Properties(const Properties& copy)
 
 
 Properties::Properties(Stream* stream)
+    : _dirPath(NULL), _parent(NULL)
 {
     readProperties(stream);
     rewind();
 }
 
-Properties::Properties(Stream* stream, const char* name, const char* id, const char* parentID) : _namespace(name)
+Properties::Properties(Stream* stream, const char* name, const char* id, const char* parentID, Properties* parent)
+    : _namespace(name), _dirPath(NULL), _parent(parent)
 {
     if (id)
     {
@@ -104,6 +108,7 @@ Properties* Properties::create(const char* url)
         p = p->clone();
         SAFE_DELETE(properties);
     }
+    p->setDirectoryPath(FileSystem::dirname(fileString.c_str()));
     return p;
 }
 
@@ -248,7 +253,7 @@ void Properties::readProperties(Stream* stream)
                     }
 
                     // New namespace without an ID.
-                    Properties* space = new Properties(stream, name, NULL, parentID);
+                    Properties* space = new Properties(stream, name, NULL, parentID, this);
                     _namespaces.push_back(space);
 
                     // If the namespace ends on this line, seek to right after the '}' character.
@@ -290,7 +295,7 @@ void Properties::readProperties(Stream* stream)
                         }
 
                         // Create new namespace.
-                        Properties* space = new Properties(stream, name, value, parentID);
+                        Properties* space = new Properties(stream, name, value, parentID, this);
                         _namespaces.push_back(space);
 
                         // If the namespace ends on this line, seek to right after the '}' character.
@@ -311,7 +316,7 @@ void Properties::readProperties(Stream* stream)
                         if (c == '{')
                         {
                             // Create new namespace.
-                            Properties* space = new Properties(stream, name, value, parentID);
+                            Properties* space = new Properties(stream, name, value, parentID, this);
                             _namespaces.push_back(space);
                         }
                         else
@@ -339,6 +344,7 @@ void Properties::readProperties(Stream* stream)
 
 Properties::~Properties()
 {
+    SAFE_DELETE(_dirPath);
     for (size_t i = 0, count = _namespaces.size(); i < count; ++i)
     {
         SAFE_DELETE(_namespaces[i]);
@@ -961,6 +967,41 @@ bool Properties::getColor(const char* name, Vector4* out) const
     return false;
 }
 
+bool Properties::getPath(const char* name, std::string* path) const
+{
+    GP_ASSERT(name && path);
+    const char* valueString = getString(name);
+    if (valueString)
+    {
+        if (FileSystem::fileExists(valueString))
+        {
+            path->assign(valueString);
+            return true;
+        }
+        else
+        {
+            const Properties* prop = this;
+            while (prop != NULL)
+            {
+                // Search for the file path relative to the bundle file
+                const std::string* dirPath = prop->_dirPath;
+                if (dirPath != NULL && !dirPath->empty())
+                {
+                    std::string relativePath = *dirPath;
+                    relativePath.append(valueString);
+                    if (FileSystem::fileExists(relativePath.c_str()))
+                    {
+                        path->assign(relativePath);
+                        return true;
+                    }
+                }
+                prop = prop->_parent;
+            }
+        }
+    }
+    return false;
+}
+
 Properties* Properties::clone()
 {
     Properties* p = new Properties();
@@ -970,17 +1011,44 @@ Properties* Properties::clone()
     p->_parentID = _parentID;
     p->_properties = _properties;
     p->_propertiesItr = p->_properties.end();
+    p->setDirectoryPath(_dirPath);
 
     for (size_t i = 0, count = _namespaces.size(); i < count; i++)
     {
         GP_ASSERT(_namespaces[i]);
-        p->_namespaces.push_back(_namespaces[i]->clone());
+        Properties* child = _namespaces[i]->clone();
+        p->_namespaces.push_back(child);
+        child->_parent = p;
     }
     p->_namespacesItr = p->_namespaces.end();
 
     return p;
 }
 
+void Properties::setDirectoryPath(const std::string* path)
+{
+    if (path)
+    {
+        setDirectoryPath(*path);
+    }
+    else
+    {
+        SAFE_DELETE(_dirPath);
+    }
+}
+
+void Properties::setDirectoryPath(const std::string& path)
+{
+    if (_dirPath == NULL)
+    {
+        _dirPath = new std::string(path);
+    }
+    else
+    {
+        _dirPath->assign(path);
+    }
+}
+
 void calculateNamespacePath(const std::string& urlString, std::string& fileString, std::vector<std::string>& namespacePath)
 {
     // If the url references a specific namespace within the file,

+ 27 - 4
gameplay/src/Properties.h

@@ -8,7 +8,7 @@
 
 namespace gameplay
 {
-
+class Properties;
 /**
  * Defines a utility for loading text files in the GamePlay "properties" files
  * and reading primitive types and GamePlay math classes out of them.
@@ -377,20 +377,38 @@ public:
      */
     bool getColor(const char* name, Vector4* out) const;
 
+    /**
+     * Gets the file path for the given property if the file exists.
+     * 
+     * This method will first search for the file relative to the working directory.
+     * If the file is not found then it will search relative to the directory the bundle file is in.
+     * 
+     * @param name The name of the property.
+     * @param path The string to copy the path to if the file exists.
+     * 
+     * @return True if the property exists and the file exists, false otherwise.
+     */
+    bool getPath(const char* name, std::string* path) const;
 
 private:
     
     /**
-     * Constructors.
+     * Constructor.
      */
     Properties();
+
+    /**
+     * Constructs the Properties class from a file.
+     *
+     * @param stream The stream used for reading the properties from file.
+     */
     Properties(Stream* stream);
     Properties(const Properties& copy);
 
     /**
-     * Constructor. Read from the beginning of namespace specified
+     * Constructor. Read from the beginning of namespace specified.
      */
-    Properties(Stream* stream, const char* name, const char* id = NULL, const char* parentID = NULL);
+    Properties(Stream* stream, const char* name, const char* id, const char* parentID, Properties* parent);
 
     void readProperties(Stream* stream);
 
@@ -407,6 +425,9 @@ private:
     // Clones the Properties object.
     Properties* clone();
 
+    void setDirectoryPath(const std::string* path);
+    void setDirectoryPath(const std::string& path);
+
     std::string _namespace;
     std::string _id;
     std::string _parentID;
@@ -414,6 +435,8 @@ private:
     std::map<std::string, std::string>::const_iterator _propertiesItr;
     std::vector<Properties*> _namespaces;
     std::vector<Properties*>::const_iterator _namespacesItr;
+    std::string* _dirPath;
+    Properties* _parent;
 };
 
 }

+ 4 - 2
gameplay/src/SceneLoader.cpp

@@ -44,9 +44,11 @@ Scene* SceneLoader::loadInternal(const char* url)
     }
 
     // Get the path to the main GPB.
-    const char* path = sceneProperties->getString("path");
-    if (path)
+    std::string path;
+    if (sceneProperties->getPath("path", &path))
+    {
         _gpbPath = path;
+    }
 
     // Build the node URL/property and animation reference tables and load the referenced files/store the inline properties objects.
     buildReferenceTables(sceneProperties);

+ 25 - 17
gameplay/src/Terrain.cpp

@@ -96,8 +96,8 @@ Terrain* Terrain::create(const char* path, Properties* properties)
         if (pHeightmap)
         {
             // Read heightmap path
-            const char* heightmap = pHeightmap->getString("path");
-            if (strlen(heightmap) == 0)
+            std::string heightmap;
+            if (!pHeightmap->getPath("path", &heightmap))
             {
                 GP_WARN("No 'path' property supplied in heightmap section of terrain definition: %s", path);
                 if (!externalProperties)
@@ -105,11 +105,11 @@ Terrain* Terrain::create(const char* path, Properties* properties)
                 return NULL;
             }
 
-            std::string ext = FileSystem::getExtension(heightmap);
+            std::string ext = FileSystem::getExtension(heightmap.c_str());
             if (ext == ".PNG")
             {
                 // Read normalized height values from heightmap image
-                heightfield = HeightField::createFromImage(heightmap, 0, 1);
+                heightfield = HeightField::createFromImage(heightmap.c_str(), 0, 1);
             }
             else if (ext == ".RAW" || ext == ".R16")
             {
@@ -124,12 +124,12 @@ Terrain* Terrain::create(const char* path, Properties* properties)
                 }
 
                 // Read normalized height values from RAW file
-                heightfield = HeightField::createFromRAW(heightmap, (unsigned int)imageSize.x, (unsigned int)imageSize.y, 0, 1);
+                heightfield = HeightField::createFromRAW(heightmap.c_str(), (unsigned int)imageSize.x, (unsigned int)imageSize.y, 0, 1);
             }
             else
             {
                 // Unsupported heightmap format
-                GP_WARN("Unsupported heightmap format ('%s') in terrain definition: %s", heightmap, path);
+                GP_WARN("Unsupported heightmap format ('%s') in terrain definition: %s", heightmap.c_str(), path);
                 if (!externalProperties)
                     SAFE_DELETE(p);
                 return NULL;
@@ -138,8 +138,8 @@ Terrain* Terrain::create(const char* path, Properties* properties)
         else
         {
             // Try to read 'heightmap' as a simple string property
-            const char* heightmap = pTerrain->getString("heightmap");
-            if (heightmap == NULL || strlen(heightmap) == 0)
+            std::string heightmap;
+            if (!pTerrain->getPath("heightmap", &heightmap))
             {
                 GP_WARN("No 'heightmap' property supplied in terrain definition: %s", path);
                 if (!externalProperties)
@@ -147,11 +147,11 @@ Terrain* Terrain::create(const char* path, Properties* properties)
                 return NULL;
             }
 
-            std::string ext = FileSystem::getExtension(heightmap);
+            std::string ext = FileSystem::getExtension(heightmap.c_str());
             if (ext == ".PNG")
             {
                 // Read normalized height values from heightmap image
-                heightfield = HeightField::createFromImage(heightmap, 0, 1);
+                heightfield = HeightField::createFromImage(heightmap.c_str(), 0, 1);
             }
             else if (ext == ".RAW" || ext == ".R16")
             {
@@ -162,7 +162,7 @@ Terrain* Terrain::create(const char* path, Properties* properties)
             }
             else
             {
-                GP_WARN("Unsupported 'heightmap' format ('%s') in terrain definition: %s.", heightmap, path);
+                GP_WARN("Unsupported 'heightmap' format ('%s') in terrain definition: %s.", heightmap.c_str(), path);
                 if (!externalProperties)
                     SAFE_DELETE(p);
                 return NULL;
@@ -301,8 +301,10 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigne
                 else
                     ++index;
 
-                const char* textureMap = NULL;
-                const char* blendMap = NULL;
+                std::string textureMap;
+                const char* textureMapPtr = NULL;
+                std::string blendMap;
+                const char* blendMapPtr = NULL;
                 Vector2 textureRepeat;
                 int blendChannel = 0;
                 int row = -1, column = -1;
@@ -312,7 +314,10 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigne
                 Properties* t = lp->getNamespace("texture", true);
                 if (t)
                 {
-                    textureMap = t->getString("path");
+                    if (t->getPath("path", &textureMap))
+                    {
+                        textureMapPtr = textureMap.c_str();
+                    }
                     if (!t->getVector2("repeat", &textureRepeat))
                         textureRepeat.set(1,1);
                 }
@@ -320,7 +325,10 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigne
                 Properties* b = lp->getNamespace("blend", true);
                 if (b)
                 {
-                    blendMap = b->getString("path");
+                    if (b->getPath("path", &blendMap))
+                    {
+                        blendMapPtr = blendMap.c_str();
+                    }
                     const char* channel = b->getString("channel");
                     if (channel && strlen(channel) > 0)
                     {
@@ -342,9 +350,9 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigne
                 if (lp->exists("column"))
                     column = lp->getInt("column");
 
-                if (!terrain->setLayer(index, textureMap, textureRepeat, blendMap, blendChannel, row, column))
+                if (!terrain->setLayer(index, textureMapPtr, textureRepeat, blendMapPtr, blendChannel, row, column))
                 {
-                    GP_WARN("Failed to load terrain layer: %s", textureMap);
+                    GP_WARN("Failed to load terrain layer: %s", textureMap.c_str());
                 }
             }
         }

+ 6 - 0
tools/encoder/CMakeLists.txt

@@ -44,6 +44,8 @@ set(APP_SRC
 	src/BoundingVolume.h
 	src/Camera.cpp
 	src/Camera.h
+    src/Constants.cpp
+    src/Constants.h
 	src/Curve.cpp
 	src/Curve.h
 	src/Curve.inl
@@ -53,6 +55,8 @@ set(APP_SRC
 	src/EncoderArguments.h
 	src/FBXSceneEncoder.cpp
 	src/FBXSceneEncoder.h
+	src/FBXUtil.cpp
+	src/FBXUtil.h
 	src/FileIO.cpp
 	src/FileIO.h
 	src/Font.cpp
@@ -99,6 +103,8 @@ set(APP_SRC
 	src/Reference.h
 	src/ReferenceTable.cpp
 	src/ReferenceTable.h
+	src/Sampler.cpp
+	src/Sampler.h
 	src/Scene.cpp
 	src/Scene.h
 	src/StringUtil.cpp

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

@@ -16,10 +16,12 @@
     <ClCompile Include="src\Base.cpp" />
     <ClCompile Include="src\BoundingVolume.cpp" />
     <ClCompile Include="src\Camera.cpp" />
+    <ClCompile Include="src\Constants.cpp" />
     <ClCompile Include="src\Curve.cpp" />
     <ClCompile Include="src\EncoderArguments.cpp" />
     <ClCompile Include="src\Effect.cpp" />
     <ClCompile Include="src\FBXSceneEncoder.cpp" />
+    <ClCompile Include="src\FBXUtil.cpp" />
     <ClCompile Include="src\FileIO.cpp" />
     <ClCompile Include="src\Font.cpp" />
     <ClCompile Include="src\GPBFile.cpp" />
@@ -34,6 +36,7 @@
     <ClCompile Include="src\MaterialParameter.cpp" />
     <ClCompile Include="src\Matrix.cpp" />
     <ClCompile Include="src\Mesh.cpp" />
+    <ClCompile Include="src\MeshSubSet.cpp" />
     <ClCompile Include="src\Model.cpp" />
     <ClCompile Include="src\MeshPart.cpp" />
     <ClCompile Include="src\MeshSkin.cpp" />
@@ -43,6 +46,7 @@
     <ClCompile Include="src\Quaternion.cpp" />
     <ClCompile Include="src\Reference.cpp" />
     <ClCompile Include="src\ReferenceTable.cpp" />
+    <ClCompile Include="src\Sampler.cpp" />
     <ClCompile Include="src\Scene.cpp" />
     <ClCompile Include="src\StringUtil.cpp" />
     <ClCompile Include="src\Transform.cpp" />
@@ -59,10 +63,12 @@
     <ClInclude Include="src\Base.h" />
     <ClInclude Include="src\BoundingVolume.h" />
     <ClInclude Include="src\Camera.h" />
+    <ClInclude Include="src\Constants.h" />
     <ClInclude Include="src\Curve.h" />
     <ClInclude Include="src\EncoderArguments.h" />
     <ClInclude Include="src\Effect.h" />
     <ClInclude Include="src\FBXSceneEncoder.h" />
+    <ClInclude Include="src\FBXUtil.h" />
     <ClInclude Include="src\FileIO.h" />
     <ClInclude Include="src\Font.h" />
     <ClInclude Include="src\GPBFile.h" />
@@ -76,6 +82,7 @@
     <ClInclude Include="src\MaterialParameter.h" />
     <ClInclude Include="src\Matrix.h" />
     <ClInclude Include="src\Mesh.h" />
+    <ClInclude Include="src\MeshSubSet.h" />
     <ClInclude Include="src\Model.h" />
     <ClInclude Include="src\MeshPart.h" />
     <ClInclude Include="src\MeshSkin.h" />
@@ -85,6 +92,7 @@
     <ClInclude Include="src\Quaternion.h" />
     <ClInclude Include="src\Reference.h" />
     <ClInclude Include="src\ReferenceTable.h" />
+    <ClInclude Include="src\Sampler.h" />
     <ClInclude Include="src\Scene.h" />
     <ClInclude Include="src\StringUtil.h" />
     <ClInclude Include="src\Thread.h" />

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

@@ -124,6 +124,18 @@
     <ClCompile Include="src\NormalMapGenerator.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\Constants.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\FBXUtil.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\MeshSubSet.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Sampler.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\VertexElement.h">
@@ -249,6 +261,18 @@
     <ClInclude Include="src\NormalMapGenerator.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\Constants.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\FBXUtil.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\MeshSubSet.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Sampler.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\Vector2.inl">

+ 31 - 0
tools/encoder/src/Constants.cpp

@@ -0,0 +1,31 @@
+#include "Constants.h"
+
+
+namespace gameplay
+{
+
+const std::string SPOT_LIGHT = "SPOT_LIGHT";
+const std::string POINT_LIGHT = "POINT_LIGHT";
+
+const std::string VERTEX_COLOR = "VERTEX_COLOR";
+
+const std::string SPECULAR = "SPECULAR";
+const std::string LINEAR = "LINEAR";
+const std::string CLAMP = "CLAMP";
+const std::string REPEAT = "REPEAT";
+const std::string LINEAR_MIPMAP_LINEAR = "LINEAR_MIPMAP_LINEAR";
+const std::string WORLD_VIEW_PROJECTION_MATRIX = "WORLD_VIEW_PROJECTION_MATRIX";
+
+const std::string TEXTURE_REPEAT = "TEXTURE_REPEAT";
+const std::string TEXTURE_OFFSET = "TEXTURE_OFFSET";
+
+const std::string u_diffuseTexture = "u_diffuseTexture";
+
+const std::string MIN_FILTER = "minFilter";
+const std::string MAG_FILTER = "magFilter";
+
+
+
+
+
+}

+ 33 - 0
tools/encoder/src/Constants.h

@@ -0,0 +1,33 @@
+#ifndef CONSTANTS_H_
+#define CONSTANTS_H_
+
+#include "string"
+
+namespace gameplay
+{
+
+extern const std::string SPOT_LIGHT;
+extern const std::string POINT_LIGHT;
+
+extern const std::string VERTEX_COLOR;
+
+extern const std::string SPECULAR;
+extern const std::string LINEAR;
+
+extern const std::string CLAMP;
+extern const std::string REPEAT;
+extern const std::string LINEAR_MIPMAP_LINEAR;
+extern const std::string WORLD_VIEW_PROJECTION_MATRIX;
+
+extern const std::string TEXTURE_REPEAT;
+extern const std::string TEXTURE_OFFSET;
+
+extern const std::string u_diffuseTexture;
+
+extern const std::string MIN_FILTER;
+extern const std::string MAG_FILTER;
+
+
+}
+
+#endif

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

@@ -24,7 +24,8 @@ EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _fontPreview(false),
     _textOutput(false),
     _optimizeAnimations(false),
-    _animationGrouping(ANIMATIONGROUP_PROMPT)
+    _animationGrouping(ANIMATIONGROUP_PROMPT),
+    _outputMaterial(false)
 {
     __instance = this;
 
@@ -298,6 +299,11 @@ bool EncoderArguments::optimizeAnimationsEnabled() const
     return _optimizeAnimations;
 }
 
+bool EncoderArguments::outputMaterialEnabled() const
+{
+    return _outputMaterial;
+}
+
 const char* EncoderArguments::getNodeId() const
 {
     if (_nodeId.length() == 0)
@@ -478,6 +484,13 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             }
         }
         break;
+    case 'm':
+        if (str.compare("-m") == 0)
+        {
+            // generate a material file
+            _outputMaterial = true;
+        }
+        break;
     case 'n':
         _normalMap = true;
         break;

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

@@ -154,6 +154,7 @@ public:
     bool fontPreviewEnabled() const;
     bool textOutputEnabled() const;
     bool optimizeAnimationsEnabled() const;
+    bool outputMaterialEnabled() const;
 
     const char* getNodeId() const;
     unsigned int getFontSize() const;
@@ -202,6 +203,7 @@ private:
     bool _textOutput;
     bool _optimizeAnimations;
     AnimationGroupOption _animationGrouping;
+    bool _outputMaterial;
 
     std::vector<std::string> _groupAnimationNodeId;
     std::vector<std::string> _groupAnimationAnimationId;

+ 458 - 973
tools/encoder/src/FBXSceneEncoder.cpp

@@ -2,195 +2,17 @@
 
 #include <algorithm>
 #include <string>
+#include <sstream>
 
 #include "FBXSceneEncoder.h"
-#include "EncoderArguments.h"
+#include "FBXUtil.h"
+#include "Sampler.h"
 
 using namespace gameplay;
-
-/**
- * Returns the aspect ratio from the given camera.
- * 
- * @param fbxCamera The FBX camera to get the aspect ratio from.
- * 
- * @return The aspect ratio from the camera.
- */
-static float getAspectRatio(FbxCamera* fbxCamera);
-
-/**
- * Returns the field of view Y from the given camera.
- * 
- * @param fbxCamera The camera to get the fiew of view from.
- * 
- * @return The field of view Y.
- */
-static float getFieldOfView(FbxCamera* fbxCamera);
-
-/**
- * Loads the texture coordinates from given mesh's polygon part into the vertex.
- * 
- * @param fbxMesh The mesh to get the polygon from.
- * @param uvs The UV list to load tex coords from.
- * @param uvSetIndex The UV set index of the uvs.
- * @param polyIndex The index of the polygon in the mesh.
- * @param posInPoly The position of the vertex in the polygon.
- * @param meshVertexIndex The index of the vertex in the mesh.
- * @param vertex The vertex to copy the texture coordinates to.
- */
-static void loadTextureCoords(FbxMesh* fbxMesh, const FbxGeometryElementUV* uvs, int uvSetIndex, int polyIndex, int posInPoly, int meshVertexIndex, Vertex* vertex);
-
-/**
- * Loads the normal from the mesh and adds it to the given vertex.
- * 
- * @param fbxMesh The mesh to get the polygon from.
- * @param vertexIndex The vertex index in the mesh.
- * @param controlPointIndex The control point index.
- * @param vertex The vertex to copy to.
- */
-static void loadNormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
-
-/**
- * Loads the tangent from the mesh and adds it to the given vertex.
- * 
- * @param fbxMesh The mesh to load from.
- * @param vertexIndex The index of the vertex within fbxMesh.
- * @param controlPointIndex The control point index.
- * @param vertex The vertex to copy to.
- */
-static void loadTangent(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
-
-/**
- * Loads the binormal from the mesh and adds it to the given vertex.
- * 
- * @param fbxMesh The mesh to load from.
- * @param vertexIndex The index of the vertex within fbxMesh.
- * @param controlPointIndex The control point index.
- * @param vertex The vertex to copy to.
- */
-static void loadBinormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
-
-/**
- * Loads the vertex diffuse color from the mesh and adds it to the given vertex.
- * 
- * @param fbxMesh The mesh to load from.
- * @param vertexIndex The index of the vertex within fbxMesh.
- * @param controlPointIndex The control point index.
- * @param vertex The vertex to copy to.
- */
-static void loadVertexColor(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
-
-/**
- * Loads the blend weight and blend indices data into the vertex.
- * 
- * @param vertexWeights List of vertex weights. The x member contains the blendIndices. The y member contains the blendWeights.
- * @param vertex The vertex to copy the blend data to.
- */
-static void loadBlendData(const std::vector<Vector2>& vertexWeights, Vertex* vertex);
-
-/**
- * Loads the blend weights and blend indices from the given mesh.
- * 
- * Each element of weights is a list of Vector2s where "x" is the blend index and "y" is the blend weight.
- * 
- * @param fbxMesh The mesh to load from.
- * @param weights List of blend weights and blend indices for each vertex.
- * 
- * @return True if this mesh has a mesh skin, false otherwise.
- */
-static bool loadBlendWeights(FbxMesh* fbxMesh, std::vector<std::vector<Vector2> >& weights);
-
-/**
- * Copies from an FBX matrix to a float[16] array.
- */
-static void copyMatrix(const FbxMatrix& fbxMatrix, float* matrix);
-
-/**
- * Copies from an FBX matrix to a gameplay matrix.
- */
-static void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix);
-
-/**
- * Finds the min and max start time and stop time of the given animation curve.
- * 
- * startTime is updated if the animation curve contains a start time that is less than startTime.
- * stopTime is updated if the animation curve contains a stop time that is greater than stopTime.
- * frameRate is updated if the animation curve contains a frame rate that is greater than frameRate.
- * 
- * @param animCurve The animation curve to read from.
- * @param startTime The min start time. (in/out)
- * @param stopTime The max stop time. (in/out)
- * @param frameRate The frame rate. (in/out)
- */
-static void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate);
-
-/**
- * Appends key frame data to the given node for the specified animation target attribute.
- * 
- * @param fbxNode The node to get the matrix transform from.
- * @param channel The aniamtion channel to write values into.
- * @param time The time of the keyframe.
- * @param scale The evaluated scale for the keyframe.
- * @param rotation The evalulated rotation for the keyframe.
- * @param translation The evalulated translation for the keyframe.
-
- */
-static void appendKeyFrame(FbxNode* fbxNode, AnimationChannel* channel, float time, const Vector3& scale, const Quaternion& rotation, const Vector3& translation);
-
-/**
- * Decomposes the given node's matrix transform at the given time and copies to scale, rotation and translation.
- * 
- * @param fbxNode The node to get the matrix transform from.
- * @param time The time to get the matrix transform from.
- * @param scale The scale to copy to.
- * @param rotation The rotation to copy to.
- * @param translation The translation to copy to.
- */
-static void decompose(FbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotation, Vector3* translation);
-
-/**
- * Creates an animation channel that targets the given node and target attribute using the given key times and key values.
- * 
- * @param fbxNode The node to target.
- * @param targetAttrib The attribute type to target.
- * @param keyTimes The key times for the animation channel.
- * @param keyValues The key values for the animation channel.
- * 
- * @return The newly created animation channel.
- */
-static AnimationChannel* createAnimationChannel(FbxNode* fbxNode, unsigned int targetAttrib, const std::vector<float>& keyTimes, const std::vector<float>& keyValues);
-
-void addScaleChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime);
-
-void addTranslateChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime);
-
-/**
- * Determines if it is possible to automatically group animations for mesh skins.
- * 
- * @param fbxScene The FBX scene to search.
- * 
- * @return True if there is at least one mesh skin that has animations that can be grouped.
- */
-bool isGroupAnimationPossible(FbxScene* fbxScene);
-bool isGroupAnimationPossible(FbxNode* fbxNode);
-bool isGroupAnimationPossible(FbxMesh* fbxMesh);
-
-/**
- * Recursively generates the tangents and binormals for all nodes that were specified in the command line arguments.
- */
-void generateTangentsAndBinormals(FbxNode* fbxNode, const EncoderArguments& arguments);
-
-FbxAnimCurve* getCurve(FbxPropertyT<FbxDouble3>& prop, FbxAnimLayer* animLayer, const char* pChannel)
-{
-#if FBXSDK_VERSION_MAJOR == 2013 && FBXSDK_VERSION_MINOR == 1
-    return prop.GetCurve<FbxAnimCurve>(animLayer, pChannel);
-#else
-    return prop.GetCurve(animLayer, pChannel);
-#endif
-}
-
-////////////////////////////////////
-// Member Functions
-////////////////////////////////////
+using std::string;
+using std::vector;
+using std::map;
+using std::ostringstream;
 
 FBXSceneEncoder::FBXSceneEncoder()
     : _groupAnimation(NULL), _autoGroupAnimations(false)
@@ -201,7 +23,7 @@ FBXSceneEncoder::~FBXSceneEncoder()
 {
 }
 
-void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments& arguments)
+void FBXSceneEncoder::write(const string& filepath, const EncoderArguments& arguments)
 {
     FbxManager* sdkManager = FbxManager::Create();
     FbxIOSettings *ios = FbxIOSettings::Create(sdkManager, IOSROOT);
@@ -232,10 +54,17 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
     }
 
     if (arguments.tangentBinormalIdCount() > 0)
+    {
         generateTangentsAndBinormals(fbxScene->GetRootNode(), arguments);
+    }
 
     print("Loading Scene.");
     loadScene(fbxScene);
+    if (arguments.outputMaterialEnabled())
+    {
+        print("Load materials");
+        loadMaterials(fbxScene);
+    }
     print("Loading animations.");
     loadAnimations(fbxScene, arguments);
     sdkManager->Destroy();
@@ -247,14 +76,14 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
         _gamePlayFile.groupMeshSkinAnimations();
     }
     
-    std::string outputFilePath = arguments.getOutputFilePath();
+    string outputFilePath = arguments.getOutputFilePath();
 
     if (arguments.textOutputEnabled())
     {
         int pos = outputFilePath.find_last_of('.');
         if (pos > 2)
         {
-            std::string path = outputFilePath.substr(0, pos);
+            string path = outputFilePath.substr(0, pos);
             path.append(".xml");
             LOG(1, "Saving debug file: %s\n", path.c_str());
             if (!_gamePlayFile.saveText(path))
@@ -271,6 +100,51 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
             LOG(1, "Error writing binary file: %s\n", outputFilePath.c_str());
         }
     }
+
+    // Write the material file
+    if (arguments.outputMaterialEnabled())
+    {
+        int pos = outputFilePath.find_last_of('.');
+        if (pos > 2)
+        {
+            string path = outputFilePath.substr(0, pos);
+            path.append(".material");
+            writeMaterial(path);
+        }
+    }
+}
+
+bool FBXSceneEncoder::writeMaterial(const string& filepath)
+{
+    FILE* file = fopen(filepath.c_str(), "w");
+    if (!file)
+    {
+        return false;
+    }
+    // Finds the base materials that are used.
+    std::set<Material*> baseMaterialsToWrite;
+    for (map<string, Material*>::iterator it = _materials.begin(); it != _materials.end(); ++it)
+    {
+        baseMaterialsToWrite.insert(it->second->getParent());
+    }
+
+    // Write the base materials that are used.
+    for (std::set<Material*>::iterator it = baseMaterialsToWrite.begin(); it != baseMaterialsToWrite.end(); ++it)
+    {
+        Material* material = *it;
+        material->writeMaterial(file);
+        fprintf(file, "\n");
+    }
+
+    // Write all of the non-base materials.
+    for (map<string, Material*>::iterator it = _materials.begin(); it != _materials.end(); ++it)
+    {
+        (*it).second->writeMaterial(file);
+        fprintf(file, "\n");
+    }
+
+    fclose(file);
+    return true;
 }
 
 void FBXSceneEncoder::loadScene(FbxScene* fbxScene)
@@ -393,7 +267,7 @@ void FBXSceneEncoder::loadAnimationChannels(FbxAnimLayer* animLayer, FbxNode* fb
     assert(stopTime >= 0.0f);
 
     // Determine which animation channels to create
-    std::vector<unsigned int> channelAttribs;
+    vector<unsigned int> channelAttribs;
     if (sx && sy && sz)
     {
         if (rx || ry || rz)
@@ -478,7 +352,7 @@ void FBXSceneEncoder::loadAnimationChannels(FbxAnimLayer* animLayer, FbxNode* fb
     assert(channelCount > 0);
 
     // Allocate channel list
-    int channelStart = animation->getAnimationChannelCount();
+    const int channelStart = animation->getAnimationChannelCount();
     for (unsigned int i = 0; i < channelCount; ++i)
     {
         AnimationChannel* channel = new AnimationChannel();
@@ -519,13 +393,12 @@ void FBXSceneEncoder::loadAnimationChannels(FbxAnimLayer* animLayer, FbxNode* fb
         // Append keyframe data to all channels
         for (unsigned int i = channelStart, channelEnd = channelStart + channelCount; i < channelEnd; ++i)
         {
-            appendKeyFrame(fbxNode, animation->getAnimationChannel(i), time, scale, rotation, translation);
+            appendKeyFrame(fbxNode, animation->getAnimationChannel(i), (float)time, scale, rotation, translation);
         }
     }
 
     if (_groupAnimation != animation)
     {
-        // TODO explain
         _gamePlayFile.addAnimation(animation);
     }
 }
@@ -570,13 +443,13 @@ void FBXSceneEncoder::loadAnimations(FbxScene* fbxScene, const EncoderArguments&
     if (!animStack)
         return;
 
-    for (int i = 0; i < fbxScene->GetSrcObjectCount(FBX_TYPE(FbxAnimStack)); ++i)
+    for (int i = 0; i < fbxScene->GetSrcObjectCount<FbxAnimStack>(); ++i)
     {
-        FbxAnimStack* animStack = FbxCast<FbxAnimStack>(fbxScene->GetSrcObject(FBX_TYPE(FbxAnimStack), i));
-        int nbAnimLayers = animStack->GetMemberCount(FBX_TYPE(FbxAnimLayer));
+        FbxAnimStack* animStack = FbxCast<FbxAnimStack>(fbxScene->GetSrcObject<FbxAnimStack>(i));
+        int nbAnimLayers = animStack->GetMemberCount<FbxAnimLayer>();
         for (int l = 0; l < nbAnimLayers; ++l)
         {
-            FbxAnimLayer* animLayer = animStack->GetMember(FBX_TYPE(FbxAnimLayer), l);
+            FbxAnimLayer* animLayer = animStack->GetMember<FbxAnimLayer>(l);
             loadAnimationLayer(animLayer, fbxScene->GetRootNode(), arguments);
         }
     }
@@ -626,13 +499,14 @@ Node* FBXSceneEncoder::loadNode(FbxNode* fbxNode)
             node->addChild(child);
         }
     }
+    _nodeMap[fbxNode] = node;
     return node;
 }
 
 Mesh* FBXSceneEncoder::getMesh(FbxUInt64 meshId)
 {
     // Check if this mesh was already loaded.
-    std::map<FbxUInt64, Mesh*>::iterator it = _meshes.find(meshId);
+    map<FbxUInt64, Mesh*>::iterator it = _meshes.find(meshId);
     if (it != _meshes.end())
     {
         return it->second;
@@ -712,6 +586,132 @@ void FBXSceneEncoder::transformNode(FbxNode* fbxNode, Node* node)
     }
 }
 
+Material* FBXSceneEncoder::getBaseMaterial(const char* id)
+{
+    map<string, Material*>::iterator it = _baseMaterials.find(string(id));
+    if (it != _baseMaterials.end())
+    {
+        return it->second;
+    }
+    return NULL;
+}
+
+static string getBaseMaterialName(Material* material)
+{
+    ostringstream baseName;
+    if (material->isTextured())
+    {
+        baseName << "Textured";
+    }
+    else
+    {
+        baseName << "Colored";
+    }
+
+    if (material->isLit())
+    {
+        if (material->isSpecular())
+        {
+            baseName << "Specular";
+        }
+    }
+    else
+    {
+        baseName << "Unlit";
+    }
+    return baseName.str();
+}
+
+Material* FBXSceneEncoder::findBaseMaterial(Material* material)
+{
+    string baseMaterialName = getBaseMaterialName(material);
+    Material* baseMaterial = getBaseMaterial(baseMaterialName.c_str());
+    if (baseMaterial)
+    {
+        return baseMaterial;
+    }
+    baseMaterial = createBaseMaterial(baseMaterialName, material);
+    _baseMaterials[baseMaterial->getId()] = baseMaterial;
+    return baseMaterial;
+}
+
+Node* FBXSceneEncoder::findNode(FbxNode* fbxNode)
+{
+    if (fbxNode)
+    {
+        map<FbxNode*, Node*>::const_iterator it = _nodeMap.find(fbxNode);
+        if (it != _nodeMap.end())
+        {
+            return it->second;
+        }
+    }
+    return NULL;
+}
+
+Material* FBXSceneEncoder::createBaseMaterial(const string& baseMaterialName, Material* childMaterial)
+{
+    Material* baseMaterial = new Material(baseMaterialName);
+    baseMaterial->setUniform("u_worldViewProjectionMatrix", "WORLD_VIEW_PROJECTION_MATRIX");
+    baseMaterial->setRenderState("cullFace", "true");
+    baseMaterial->setRenderState("depthTest", "true");
+    if (childMaterial->isLit())
+    {
+        baseMaterial->setLit(true);
+        baseMaterial->setUniform("u_inverseTransposeWorldViewMatrix", "INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX");
+        // Always use directional light
+        baseMaterial->setUniform("u_lightDirection", "SCENE_LIGHT_DIRECTION");
+        baseMaterial->setUniform("u_lightColor", "SCENE_LIGHT_COLOR");
+
+        if (childMaterial->isSpecular())
+        {
+            baseMaterial->addDefine(SPECULAR);
+            baseMaterial->setUniform("u_cameraPosition", "CAMERA_WORLD_POSITION");
+        }
+    }
+    if (childMaterial->isTextured())
+    {
+        if (childMaterial->isLit())
+        {
+            if (childMaterial->isBumped())
+            {
+                baseMaterial->setVertexShader("res/shaders/textured-bumped.vert");
+                baseMaterial->setFragmentShader("res/shaders/textured-bumped.frag");
+            }
+            else
+            {
+                baseMaterial->setVertexShader("res/shaders/textured.vert");
+                baseMaterial->setFragmentShader("res/shaders/textured.frag");
+            }
+        }
+        else
+        {
+            baseMaterial->setVertexShader("res/shaders/textured-unlit.vert");
+            baseMaterial->setFragmentShader("res/shaders/textured-unlit.frag");
+        }
+        Sampler* sampler = baseMaterial->createSampler(u_diffuseTexture);
+        sampler->set("mipmap", "true");
+        sampler->set("wrapS", CLAMP);
+        sampler->set("wrapT", CLAMP);
+        sampler->set(MIN_FILTER, LINEAR_MIPMAP_LINEAR);
+        sampler->set(MAG_FILTER, LINEAR);
+    }
+    else
+    {
+        if (childMaterial->isLit())
+        {
+            baseMaterial->setVertexShader("res/shaders/colored.vert");
+            baseMaterial->setFragmentShader("res/shaders/colored.frag");
+        }
+        else
+        {
+            baseMaterial->setVertexShader("res/shaders/colored-unlit.vert");
+            baseMaterial->setFragmentShader("res/shaders/colored-unlit.frag");
+        }
+    }
+    assert(baseMaterial);
+    return baseMaterial;
+}
+
 void FBXSceneEncoder::loadBindShapes(FbxScene* fbxScene)
 {
     float m[16];
@@ -751,7 +751,7 @@ void FBXSceneEncoder::loadCamera(FbxNode* fbxNode, Node* node)
     const char* name = fbxNode->GetName();
     if (name)
     {
-        std::string id(name);
+        string id(name);
         id.append("_Camera");
         camera->setId(id);
     }
@@ -792,7 +792,7 @@ void FBXSceneEncoder::loadLight(FbxNode* fbxNode, Node* node)
     const char* name = fbxNode->GetName();
     if (name)
     {
-        std::string id(name);
+        string id(name);
         id.append("_Light");
         light->setId(id);
     }
@@ -851,7 +851,7 @@ void FBXSceneEncoder::loadLight(FbxNode* fbxNode, Node* node)
             break;
         case FbxLight::eLinear:
             light->setLinearAttenuation((float)fbxLight->DecayStart.Get());
-            break;  
+            break;
         case FbxLight::eQuadratic:
             light->setQuadraticAttenuation((float)fbxLight->DecayStart.Get());
             break;
@@ -890,12 +890,257 @@ void FBXSceneEncoder::loadModel(FbxNode* fbxNode, Node* node)
         loadSkin(fbxMesh, model);
         if (model->getSkin())
         {
-            // TODO: explain
             node->resetTransformMatrix();
         }
     }
 }
 
+void FBXSceneEncoder::loadMaterials(FbxScene* fbxScene)
+{
+    FbxNode* rootNode = fbxScene->GetRootNode();
+    if (rootNode)
+    {
+        // Don't include the FBX root node
+        const int childCount = rootNode->GetChildCount();
+        for (int i = 0; i < childCount; ++i)
+        {
+            FbxNode* fbxNode = rootNode->GetChild(i);
+            if (fbxNode)
+            {
+                loadMaterial(fbxNode);
+            }
+        }
+    }
+}
+
+void FBXSceneEncoder::loadMaterial(FbxNode* fbxNode)
+{
+    Node* node = findNode(fbxNode);
+    Model* model = (node) ? node->getModel() : NULL;
+
+    const int materialCount = fbxNode->GetMaterialCount();
+    for (int index = 0; index < materialCount; ++index)
+    {
+        FbxSurfaceMaterial* fbxMaterial = fbxNode->GetMaterial(index);
+        const string materialName(fbxMaterial->GetName());
+        Material* material = NULL;
+        map<string, Material*>::iterator it = _materials.find(materialName);
+        if (it != _materials.end())
+        {
+            // This material was already loaded so don't load it again
+            material = it->second;
+        }
+        else
+        {
+            material = createMaterial(materialName, fbxMaterial, node);
+            _materials[materialName] = material;
+        }
+
+        if (materialCount == 1 && material && model)
+        {
+            model->setMaterial(material); // TODO: add support for materials per mesh part
+        }
+        else if (materialCount > 1 && material && model)
+        {
+            model->setMaterial(material, index);
+        }
+    }
+
+    const int childCount = fbxNode->GetChildCount();
+    for (int i = 0; i < childCount; ++i)
+    {
+        FbxNode* childNode = fbxNode->GetChild(i);
+        if (childNode)
+        {
+            loadMaterial(childNode);
+        }
+    }
+}
+
+void FBXSceneEncoder::loadMaterialTextures(FbxSurfaceMaterial* fbxMaterial, Material* material)
+{
+    FbxProperty fbxProperty;
+    int textureIndex;
+    FBXSDK_FOR_EACH_TEXTURE(textureIndex)
+    {
+        fbxProperty = fbxMaterial->FindProperty(FbxLayerElement::sTextureChannelNames[textureIndex]);
+        //FindAndDisplayTextureInfoByProperty(fbxProperty, lDisplayHeader, lMaterialIndex);
+        if ( fbxProperty.IsValid() )
+        {
+            int textureCount = fbxProperty.GetSrcObjectCount<FbxTexture>();
+            for (int j = 0; j < textureCount; ++j)
+            {
+                FbxLayeredTexture *layeredTexture = fbxProperty.GetSrcObject<FbxLayeredTexture>(j);
+                if (layeredTexture)
+                {
+                    //DisplayInt("    Layered Texture: ", j);
+                    FbxLayeredTexture *layeredTexture = fbxProperty.GetSrcObject<FbxLayeredTexture>(j);
+                    int lNbTextures = layeredTexture->GetSrcObjectCount<FbxTexture>();
+                    for (int k = 0; k<lNbTextures; ++k)
+                    {
+                        FbxTexture* fbxTexture = layeredTexture->GetSrcObject<FbxTexture>(k);
+                        if (fbxTexture)
+                        {
+                            /*
+                            if (pDisplayHeader){
+                                DisplayInt("    Textures connected to Material ", pMaterialIndex);
+                                pDisplayHeader = false;
+                            }
+                            */
+
+                            //NOTE the blend mode is ALWAYS on the LayeredTexture and NOT the one on the texture.
+                            //Why is that?  because one texture can be shared on different layered textures and might
+                            //have different blend modes.
+
+                            FbxLayeredTexture::EBlendMode lBlendMode;
+                            layeredTexture->GetTextureBlendMode(k, lBlendMode);
+                            //DisplayString("    Textures for ", pProperty.GetName());
+                            //DisplayInt("        Texture ", k);
+                            //DisplayTextureInfo(fbxTexture, (int) lBlendMode);
+                        }
+
+                    }
+                }
+                else if (FbxTexture* fbxTexture = fbxProperty.GetSrcObject<FbxTexture>(j))
+                {
+                    //no layered texture simply get on the property
+                    if (FbxFileTexture* fileTexture = FbxCast<FbxFileTexture>(fbxTexture))
+                    {
+                        loadMaterialFileTexture(fileTexture, material);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void FBXSceneEncoder::loadMaterialFileTexture(FbxFileTexture* fileTexture, Material* material)
+{
+    FbxTexture::ETextureUse textureUse = fileTexture->GetTextureUse();
+    Sampler* sampler = NULL;
+    if (textureUse == FbxTexture::eStandard)
+    {
+        if (!material->getSampler("u_diffuseTexture"))
+            sampler = material->createSampler("u_diffuseTexture");
+    }
+    else if (textureUse == FbxTexture::eBumpNormalMap)
+    {
+        if (!material->getSampler("u_normalmapTexture"))
+            sampler = material->createSampler("u_normalmapTexture");
+    }
+    if (sampler)
+    {
+        sampler->set("absolutePath", fileTexture->GetFileName());
+        sampler->set("relativePath", fileTexture->GetRelativeFileName());
+        sampler->set("wrapS", fileTexture->GetWrapModeU() == FbxTexture::eClamp ? CLAMP : REPEAT);
+        sampler->set("wrapT", fileTexture->GetWrapModeV() == FbxTexture::eClamp ? CLAMP : REPEAT);
+        //sampler->set(MIN_FILTER, LINEAR_MIPMAP_LINEAR);
+        //sampler->set(MAG_FILTER, LINEAR);
+
+        if (textureUse == FbxTexture::eStandard)
+        {
+            double scaleU = fileTexture->GetScaleU();
+            double scaleV = fileTexture->GetScaleV();
+            if (scaleU != 1 || scaleV != 1)
+            {
+                ostringstream stream;
+                stream << scaleU << ", " << scaleV;
+                material->setUniform("u_textureRepeat", stream.str());
+                material->addDefine(TEXTURE_REPEAT);
+            }
+
+            double translationU = fileTexture->GetTranslationU();
+            double translationV = fileTexture->GetTranslationV();
+            if (translationU != 0 || translationV != 0)
+            {
+                ostringstream stream;
+                stream << translationU << ", " << translationV;
+                material->setUniform("u_textureOffset", stream.str());
+                material->addDefine(TEXTURE_OFFSET);
+            }
+        }
+    }
+}
+
+void FBXSceneEncoder::loadMaterialUniforms(FbxSurfaceMaterial* fbxMaterial, Material* material)
+{
+    
+    if ( fbxMaterial->GetClassId().Is(FbxSurfaceLambert::ClassId) )
+    {
+        FbxSurfaceLambert* lambert = FbxCast<FbxSurfaceLambert>(fbxMaterial);
+
+        if (material->isLit())
+        {
+            FbxDouble3 ambient = lambert->Ambient.Get();
+            if (!isBlack(ambient))
+            {
+                material->setUniform("u_ambientColor", toString(ambient));
+            }
+        }
+        if (!material->isTextured())
+        {
+            if (!material->isDefined(VERTEX_COLOR))
+            {
+                FbxDouble3 diffuse = lambert->Diffuse.Get();
+                if (!isBlack(diffuse))
+                {
+                    material->setUniform("u_diffuseColor", toString(diffuse, 1.0));
+                }
+            }
+        }
+    }
+    if (fbxMaterial->GetClassId().Is(FbxSurfacePhong::ClassId))
+    {
+        FbxSurfacePhong* phong = FbxCast<FbxSurfacePhong>(fbxMaterial);
+        //FbxDouble specularFactor = phong->SpecularFactor.Get();
+        if (material->isLit())
+        {
+            FbxDouble shininess = phong->Shininess.Get();
+            if (shininess > 0)
+            {
+                ostringstream stream;
+                stream << shininess;
+                material->setUniform("u_specularExponent", stream.str());
+                material->addDefine(SPECULAR);
+            }
+        }
+        //
+        //((FbxSurfacePhong *) fbxMaterial)->GetAmbientColor();
+        //((FbxSurfacePhong *) fbxMaterial)->GetDiffuseColor();
+    }
+}
+
+Material* FBXSceneEncoder::createMaterial(const string& name, FbxSurfaceMaterial* fbxMaterial, Node* node)
+{
+    assert(fbxMaterial);
+    Material* material = new Material(name);
+    Model* model = (node) ? node->getModel() : NULL;
+    Mesh* mesh = (model) ? model->getMesh() : NULL;
+    if (mesh)
+    {
+        // The material should be lit if the model has normals or there are lights in the scene.
+        material->setLit(mesh->hasNormals() || _gamePlayFile.getLightCount() > 0);
+        if (mesh->hasVertexColors())
+        {
+            material->addDefine(VERTEX_COLOR);
+        }
+    }
+    MeshSkin* skin = (model) ? model->getSkin() : NULL;
+    if (skin && skin->getJointCount() > 0)
+    {
+        material->setUniform("u_matrixPalette", "MATRIX_PALETTE");
+        material->addDefine("SKINNING");
+        ostringstream stream;
+        stream << "SKINNING_JOINT_COUNT " << skin->getJointCount();
+        material->addDefine(stream.str());
+    }
+    loadMaterialTextures(fbxMaterial, material);
+    loadMaterialUniforms(fbxMaterial, material);
+    material->setParent(findBaseMaterial(material));
+    assert(material);
+    return material;
+}
+
 void FBXSceneEncoder::loadSkin(FbxMesh* fbxMesh, Model* model)
 {
     const int deformerCount = fbxMesh->GetDeformerCount();
@@ -904,13 +1149,13 @@ void FBXSceneEncoder::loadSkin(FbxMesh* fbxMesh, Model* model)
         FbxDeformer* deformer = fbxMesh->GetDeformer(i);
         if (deformer->GetDeformerType() == FbxDeformer::eSkin)
         {
-            FbxSkin* fbxSkin = static_cast<FbxSkin*>(deformer);
+            FbxSkin* fbxSkin = FbxCast<FbxSkin>(deformer);
 
             MeshSkin* skin = new MeshSkin();
 
-            std::vector<std::string> jointNames;
-            std::vector<Node*> joints;
-            std::vector<Matrix> bindPoses;
+            vector<string> jointNames;
+            vector<Node*> joints;
+            vector<Matrix> bindPoses;
 
             const int clusterCount = fbxSkin->GetClusterCount();
             for (int j = 0; j < clusterCount; ++j)
@@ -956,14 +1201,14 @@ Mesh* FBXSceneEncoder::loadMesh(FbxMesh* fbxMesh)
     const char* name = fbxMesh->GetNode()->GetName();
     if (name)
     {
-        std::string id(name);
+        string id(name);
         id.append("_Mesh");
         mesh->setId(id);
     }
 
     // The number of mesh parts is equal to the number of materials that affect this mesh.
     // There is always at least one mesh part.
-    std::vector<MeshPart*> meshParts;
+    vector<MeshPart*> meshParts;
     const int materialCount = fbxMesh->GetNode()->GetMaterialCount();
     int meshPartSize = (materialCount > 0) ? materialCount : 1;
     for (int i = 0; i < meshPartSize; ++i)
@@ -972,7 +1217,7 @@ Mesh* FBXSceneEncoder::loadMesh(FbxMesh* fbxMesh)
     }
 
     // Find the blend weights and blend indices if this mesh is skinned.
-    std::vector<std::vector<Vector2> > weights;
+    vector<vector<Vector2> > weights;
     bool hasSkin = loadBlendWeights(fbxMesh, weights);
     
     // Get list of uv sets for mesh
@@ -1096,13 +1341,13 @@ void FBXSceneEncoder::triangulateRecursive(FbxNode* fbxNode)
 {
     // Triangulate all NURBS, patch and mesh under this node recursively.
     FbxNodeAttribute* nodeAttribute = fbxNode->GetNodeAttribute();
-
     if (nodeAttribute)
     {
-        if (nodeAttribute->GetAttributeType() == FbxNodeAttribute::eMesh ||
-            nodeAttribute->GetAttributeType() == FbxNodeAttribute::eNurbs ||
-            nodeAttribute->GetAttributeType() == FbxNodeAttribute::eNurbsSurface ||
-            nodeAttribute->GetAttributeType() == FbxNodeAttribute::ePatch)
+        FbxNodeAttribute::EType type = nodeAttribute->GetAttributeType();
+        if (type == FbxNodeAttribute::eMesh ||
+            type == FbxNodeAttribute::eNurbs ||
+            type == FbxNodeAttribute::eNurbsSurface ||
+            type == FbxNodeAttribute::ePatch)
         {
             FbxGeometryConverter converter(fbxNode->GetFbxManager());
             converter.TriangulateInPlace(fbxNode);
@@ -1116,764 +1361,4 @@ void FBXSceneEncoder::triangulateRecursive(FbxNode* fbxNode)
     }
 }
 
-////////////////////////////////////
-// Functions
-////////////////////////////////////
-
-float getAspectRatio(FbxCamera* fbxCamera)
-{
-    return (float)fbxCamera->FilmAspectRatio.Get();
-    /*
-    FbxCamera::ECameraAspectRatioMode camAspectRatioMode = fbxCamera->GetAspectRatioMode();
-    double aspectX = fbxCamera->AspectWidth.Get();
-    double aspectY = fbxCamera->AspectHeight.Get();
-    double aspectRatio = 1.333333;
-    switch ( camAspectRatioMode)
-    {
-    case FbxCamera::eWINDOW_SIZE:
-        aspectRatio = aspectX / aspectY;
-        break;
-    case FbxCamera::eFIXED_RATIO:
-        aspectRatio = aspectX;
-        break;
-    case FbxCamera::eFIXED_RESOLUTION:
-        aspectRatio = aspectX / aspectY * fbxCamera->GetPixelRatio();
-        break;
-    case FbxCamera::eFIXED_WIDTH:
-        aspectRatio = fbxCamera->GetPixelRatio() / aspectY;
-        break;
-    case FbxCamera::eFIXED_HEIGHT:
-        aspectRatio = fbxCamera->GetPixelRatio() * aspectX;
-        break;
-    default:
-        break;
-    }
-    return (float)aspectRatio;
-    */
-}
-
-inline double vfov(double hfov, double aspect)
-{
-    static const double MATH_PI_180 = 0.01745329251994329576923690768489;
-    static const double MATH_180_PI = 57.295779513082320876798154814105;
-    return (2.0 * atan((aspect) * tan( (hfov * MATH_PI_180) * 0.5)) * MATH_180_PI);
-}
-
-float getFieldOfView(FbxCamera* fbxCamera)
-{
-    double fieldOfViewX = 0.0;
-    double fieldOfViewY = 0.0;
-    double filmHeight = fbxCamera->GetApertureHeight();
-    double filmWidth = fbxCamera->GetApertureWidth() * fbxCamera->GetSqueezeRatio();
-    double apertureRatio = filmHeight / filmWidth;
-    if ( fbxCamera->GetApertureMode() == FbxCamera::eVertical)
-    {
-        fieldOfViewY = fbxCamera->FieldOfView.Get();
-    }
-    else if (fbxCamera->GetApertureMode() == FbxCamera::eHorizontal)
-    {
-        fieldOfViewX = fbxCamera->FieldOfView.Get();
-        fieldOfViewY = vfov( fieldOfViewX, apertureRatio);
-    }
-    else if (fbxCamera->GetApertureMode() == FbxCamera::eFocalLength)
-    {
-        fieldOfViewX = fbxCamera->ComputeFieldOfView(fbxCamera->FocalLength.Get());
-        fieldOfViewY = vfov( fieldOfViewX, apertureRatio);
-    }
-    else if (fbxCamera->GetApertureMode() == FbxCamera::eHorizAndVert)
-    {
-        fieldOfViewY = fbxCamera->FieldOfViewY.Get();
-    }
-    else
-    {
-        fieldOfViewY = 45.0;
-    }
-    return (float)fieldOfViewY;
-}
-
-void loadTextureCoords(FbxMesh* fbxMesh, const FbxGeometryElementUV* uvs, int uvSetIndex, int polyIndex, int posInPoly, int meshVertexIndex, Vertex* vertex)
-{
-    assert(fbxMesh && polyIndex >=0 && posInPoly >= 0);
-
-    const bool useIndex = uvs->GetReferenceMode() != FbxGeometryElement::eDirect;
-    const int indexCount = useIndex ? uvs->GetIndexArray().GetCount() : 0;
-    int uvIndex = -1;
-
-    switch (uvs->GetMappingMode())
-    {
-    case FbxGeometryElement::eByControlPoint:
-        {
-            // Get the index of the current vertex in control points array
-            int polyVertIndex = fbxMesh->GetPolygonVertex(polyIndex, posInPoly);
-
-            // The UV index depends on the reference mode
-            uvIndex = useIndex ? uvs->GetIndexArray().GetAt(polyVertIndex) : polyVertIndex;
-        }
-        break;
-
-    case FbxGeometryElement::eByPolygonVertex:
-        if (meshVertexIndex < indexCount)
-        {
-            uvIndex = useIndex ? uvs->GetIndexArray().GetAt(meshVertexIndex) : meshVertexIndex;
-        }
-        break;
-
-    default:
-        // Only support eByPolygonVertex and eByControlPoint mappings
-        break;
-    }
-
-    vertex->hasTexCoord[uvSetIndex] = true;
-
-    // Store UV information in vertex
-    if (uvIndex != -1)
-    {
-        FbxVector2 uvValue = uvs->GetDirectArray().GetAt(uvIndex);
-        vertex->texCoord[uvSetIndex].x = (float)uvValue[0];
-        vertex->texCoord[uvSetIndex].y = (float)uvValue[1];
-    }
-}
-
-void loadNormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
-{
-    if (fbxMesh->GetElementNormalCount() > 0)
-    {
-        // Get only the first
-        FbxGeometryElementNormal* normal = fbxMesh->GetElementNormal(0);
-        FbxGeometryElement::EMappingMode mappingMode = normal->GetMappingMode();
-        if (mappingMode == FbxGeometryElement::eByControlPoint)
-        {
-            switch (normal->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(controlPointIndex);
-                    vertex->hasNormal = true;
-                    vertex->normal.x = (float)vec4[0];
-                    vertex->normal.y = (float)vec4[1];
-                    vertex->normal.z = (float)vec4[2];
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = normal->GetIndexArray().GetAt(controlPointIndex);
-                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(id);
-                    vertex->hasNormal = true;
-                    vertex->normal.x = (float)vec4[0];
-                    vertex->normal.y = (float)vec4[1];
-                    vertex->normal.z = (float)vec4[2];
-                }
-                break;
-            default:
-                break;
-            }
-        }
-        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
-        {
-            switch (normal->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(vertexIndex);
-                    vertex->hasNormal = true;
-                    vertex->normal.x = (float)vec4[0];
-                    vertex->normal.y = (float)vec4[1];
-                    vertex->normal.z = (float)vec4[2];
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = normal->GetIndexArray().GetAt(vertexIndex);
-                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(id);
-                    vertex->hasNormal = true;
-                    vertex->normal.x = (float)vec4[0];
-                    vertex->normal.y = (float)vec4[1];
-                    vertex->normal.z = (float)vec4[2];
-                }
-                break;
-            default:
-                break;
-            }
-        }
-    }
-}
-
-void loadTangent(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
-{
-    if (fbxMesh->GetElementTangentCount() > 0)
-    {
-        // Get only the first tangent
-        FbxGeometryElementTangent* tangent = fbxMesh->GetElementTangent(0);
-        FbxGeometryElement::EMappingMode mappingMode = tangent->GetMappingMode();
-        if (mappingMode == FbxGeometryElement::eByControlPoint)
-        {
-            switch (tangent->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(controlPointIndex);
-                    vertex->hasTangent = true;
-                    vertex->tangent.x = (float)vec4[0];
-                    vertex->tangent.y = (float)vec4[1];
-                    vertex->tangent.z = (float)vec4[2];
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = tangent->GetIndexArray().GetAt(controlPointIndex);
-                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(id);
-                    vertex->hasTangent = true;
-                    vertex->tangent.x = (float)vec4[0];
-                    vertex->tangent.y = (float)vec4[1];
-                    vertex->tangent.z = (float)vec4[2];
-                }
-                break;
-            default:
-                break;
-            }
-        }
-        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
-        {
-            switch (tangent->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(vertexIndex);
-                    vertex->hasTangent = true;
-                    vertex->tangent.x = (float)vec4[0];
-                    vertex->tangent.y = (float)vec4[1];
-                    vertex->tangent.z = (float)vec4[2];
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = tangent->GetIndexArray().GetAt(vertexIndex);
-                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(id);
-                    vertex->hasTangent = true;
-                    vertex->tangent.x = (float)vec4[0];
-                    vertex->tangent.y = (float)vec4[1];
-                    vertex->tangent.z = (float)vec4[2];
-                }
-                break;
-            default:
-                break;
-            }
-        }
-    }
-}
-
-void loadBinormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
-{
-    if (fbxMesh->GetElementBinormalCount() > 0)
-    {
-        // Get only the first binormal.
-        FbxGeometryElementBinormal* binormal = fbxMesh->GetElementBinormal(0);
-        FbxGeometryElement::EMappingMode mappingMode = binormal->GetMappingMode();
-
-        if (mappingMode == FbxGeometryElement::eByControlPoint)
-        {
-            switch (binormal->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(controlPointIndex);
-                    vertex->hasBinormal = true;
-                    vertex->binormal.x = (float)vec4[0];
-                    vertex->binormal.y = (float)vec4[1];
-                    vertex->binormal.z = (float)vec4[2];
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = binormal->GetIndexArray().GetAt(controlPointIndex);
-                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(id);
-                    vertex->hasBinormal = true;
-                    vertex->binormal.x = (float)vec4[0];
-                    vertex->binormal.y = (float)vec4[1];
-                    vertex->binormal.z = (float)vec4[2];
-                }
-                break;
-            default:
-                break;
-            }
-        }
-        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
-        {
-            switch (binormal->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(vertexIndex);
-                    vertex->hasBinormal = true;
-                    vertex->binormal.x = (float)vec4[0];
-                    vertex->binormal.y = (float)vec4[1];
-                    vertex->binormal.z = (float)vec4[2];
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = binormal->GetIndexArray().GetAt(vertexIndex);
-                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(id);
-                    vertex->hasBinormal = true;
-                    vertex->binormal.x = (float)vec4[0];
-                    vertex->binormal.y = (float)vec4[1];
-                    vertex->binormal.z = (float)vec4[2];
-                }
-                break;
-            default:
-                break;
-            }
-        }
-    }
-}
-
-void loadVertexColor(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
-{
-    if (fbxMesh->GetElementVertexColorCount() > 0)
-    {
-        // Get only the first vertex color.
-        FbxGeometryElementVertexColor* vertexColor = fbxMesh->GetElementVertexColor(0);
-        FbxGeometryElement::EMappingMode mappingMode = vertexColor->GetMappingMode();
-        if (mappingMode == FbxGeometryElement::eByControlPoint)
-        {
-            switch (vertexColor->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxColor color = vertexColor->GetDirectArray().GetAt(controlPointIndex);
-
-                    vertex->hasDiffuse = true;
-                    vertex->diffuse.x = (float)color.mRed;
-                    vertex->diffuse.y = (float)color.mGreen;
-                    vertex->diffuse.z = (float)color.mBlue;
-                    vertex->diffuse.w = (float)color.mAlpha;
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = vertexColor->GetIndexArray().GetAt(controlPointIndex);
-                    FbxColor color = vertexColor->GetDirectArray().GetAt(id);
-
-                    vertex->hasDiffuse = true;
-                    vertex->diffuse.x = (float)color.mRed;
-                    vertex->diffuse.y = (float)color.mGreen;
-                    vertex->diffuse.z = (float)color.mBlue;
-                    vertex->diffuse.w = (float)color.mAlpha;
-                }
-                break;
-            default:
-                break;
-            }
-        }
-        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
-        {
-            switch (vertexColor->GetReferenceMode())
-            {
-            case FbxGeometryElement::eDirect:
-                {
-                    FbxColor color = vertexColor->GetDirectArray().GetAt(vertexIndex);
-
-                    vertex->hasDiffuse = true;
-                    vertex->diffuse.x = (float)color.mRed;
-                    vertex->diffuse.y = (float)color.mGreen;
-                    vertex->diffuse.z = (float)color.mBlue;
-                    vertex->diffuse.w = (float)color.mAlpha;
-                }
-                break;
-            case FbxGeometryElement::eIndexToDirect:
-                {
-                    int id = vertexColor->GetIndexArray().GetAt(vertexIndex);
-                    FbxColor color = vertexColor->GetDirectArray().GetAt(id);
-
-                    vertex->hasDiffuse = true;
-                    vertex->diffuse.x = (float)color.mRed;
-                    vertex->diffuse.y = (float)color.mGreen;
-                    vertex->diffuse.z = (float)color.mBlue;
-                    vertex->diffuse.w = (float)color.mAlpha;
-                }
-                break;
-            default:
-                break;
-            }
-        }
-    }
-}
-
-void loadBlendData(const std::vector<Vector2>& vertexWeights, Vertex* vertex)
-{
-    size_t size = vertexWeights.size();
-
-    if (size >= 1)
-    {
-        vertex->hasWeights= true;
-        vertex->blendIndices.x = vertexWeights[0].x;
-        vertex->blendWeights.x = vertexWeights[0].y;
-    }
-    if (size >= 2)
-    {
-        vertex->blendIndices.y = vertexWeights[1].x;
-        vertex->blendWeights.y = vertexWeights[1].y;
-    }
-    if (size >= 3)
-    {
-        vertex->blendIndices.z = vertexWeights[2].x;
-        vertex->blendWeights.z = vertexWeights[2].y;
-    }
-    if (size >= 4)
-    {
-        vertex->blendIndices.w = vertexWeights[3].x;
-        vertex->blendWeights.w = vertexWeights[3].y;
-    }
-    //vertex->normalizeBlendWeight();
-}
-
-bool loadBlendWeights(FbxMesh* fbxMesh, std::vector<std::vector<Vector2> >& weights)
-{
-    assert(fbxMesh);
-    const int vertexCount = fbxMesh->GetControlPointsCount();
-
-    FbxSkin* fbxSkin = NULL;
-    const int deformerCount = fbxMesh->GetDeformerCount();
-    for (int i = 0; i < deformerCount; ++i)
-    {
-        FbxDeformer* deformer = fbxMesh->GetDeformer(i);
-        if (deformer->GetDeformerType() == FbxDeformer::eSkin)
-        {
-            fbxSkin = static_cast<FbxSkin*>(deformer);
-            weights.resize(vertexCount);
-
-            const int clusterCount = fbxSkin->GetClusterCount();
-            for (int j = 0; j < clusterCount; ++j)
-            {
-                FbxCluster* cluster = fbxSkin->GetCluster(j);
-                assert(cluster);
-                const int vertexIndexCount = cluster->GetControlPointIndicesCount();
-                for (int k = 0; k < vertexIndexCount; ++k)
-                {
-                    int index = cluster->GetControlPointIndices()[k];
-                    if (index >= vertexCount)
-                    {
-                        continue;
-                    }
-
-                    double weight = cluster->GetControlPointWeights()[k];
-                    if (weight == 0.0)
-                    {
-                        continue;
-                    }
-                    weights[index].push_back(Vector2((float)j, (float)weight));
-                }
-            }
-            // Only the first skin deformer will be loaded.
-            // There probably won't be more than one.
-            break;
-        }
-    }
-    return fbxSkin != NULL;
-}
-
-void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate)
-{
-    FbxTime start, stop;
-    FbxTimeSpan timeSpan;
-    animCurve->GetTimeInterval(timeSpan);
-    start = timeSpan.GetStart();
-    stop = timeSpan.GetStop();
-    *startTime = std::min(*startTime, (float)start.GetMilliSeconds());
-    *stopTime = std::max(*stopTime, (float)stop.GetMilliSeconds());
-    *frameRate = std::max(*frameRate, (float)stop.GetFrameRate(FbxTime::eDefaultMode));
-}
-
-void appendKeyFrame(FbxNode* fbxNode, AnimationChannel* channel, float time, const Vector3& scale, const Quaternion& rotation, const Vector3& translation)
-{
-    // Write key time
-    channel->getKeyTimes().push_back(time);
-
-    // Write key values
-    std::vector<float>& keyValues = channel->getKeyValues();
-    switch (channel->getTargetAttribute())
-    {
-        case Transform::ANIMATE_SCALE:
-        {
-            keyValues.push_back(scale.x);
-            keyValues.push_back(scale.y);
-            keyValues.push_back(scale.z);
-        }
-        break;
-
-        case Transform::ANIMATE_SCALE_X:
-        {
-            keyValues.push_back(scale.x);
-        }
-        break;
-
-        case Transform::ANIMATE_SCALE_Y:
-        {
-            keyValues.push_back(scale.y);
-        }
-        break;
-
-        case Transform::ANIMATE_SCALE_Z:
-        {
-            keyValues.push_back(scale.z);
-        }
-        break;
-
-        case Transform::ANIMATE_ROTATE:
-        {
-            keyValues.push_back(rotation.x);
-            keyValues.push_back(rotation.y);
-            keyValues.push_back(rotation.z);
-            keyValues.push_back(rotation.w);
-        }
-        break;
-
-        case Transform::ANIMATE_TRANSLATE:
-        {
-            keyValues.push_back(translation.x);
-            keyValues.push_back(translation.y);
-            keyValues.push_back(translation.z);
-        }
-        break;
-
-        case Transform::ANIMATE_TRANSLATE_X:
-        {
-            keyValues.push_back(translation.x);
-        }
-        break;
-
-        case Transform::ANIMATE_TRANSLATE_Y:
-        {
-            keyValues.push_back(translation.y);
-        }
-        break;
-
-        case Transform::ANIMATE_TRANSLATE_Z:
-        {
-            keyValues.push_back(translation.z);
-        }
-        break;
-
-        case Transform::ANIMATE_ROTATE_TRANSLATE:
-        {
-            keyValues.push_back(rotation.x);
-            keyValues.push_back(rotation.y);
-            keyValues.push_back(rotation.z);
-            keyValues.push_back(rotation.w);
-            keyValues.push_back(translation.x);
-            keyValues.push_back(translation.y);
-            keyValues.push_back(translation.z);
-        }
-        break;
-
-        case Transform::ANIMATE_SCALE_ROTATE_TRANSLATE:
-        {
-            keyValues.push_back(scale.x);
-            keyValues.push_back(scale.y);
-            keyValues.push_back(scale.z);
-            keyValues.push_back(rotation.x);
-            keyValues.push_back(rotation.y);
-            keyValues.push_back(rotation.z);
-            keyValues.push_back(rotation.w);
-            keyValues.push_back(translation.x);
-            keyValues.push_back(translation.y);
-            keyValues.push_back(translation.z);
-        }
-        break;
-
-        case Transform::ANIMATE_SCALE_TRANSLATE:
-        {
-            keyValues.push_back(scale.x);
-            keyValues.push_back(scale.y);
-            keyValues.push_back(scale.z);
-            keyValues.push_back(translation.x);
-            keyValues.push_back(translation.y);
-            keyValues.push_back(translation.z);
-        }
-        break;
-
-        case Transform::ANIMATE_SCALE_ROTATE:
-        {
-            keyValues.push_back(scale.x);
-            keyValues.push_back(scale.y);
-            keyValues.push_back(scale.z);
-            keyValues.push_back(rotation.x);
-            keyValues.push_back(rotation.y);
-            keyValues.push_back(rotation.z);
-            keyValues.push_back(rotation.w);
-        }
-        break;
-
-        default:
-        {
-            LOG(1, "Warning: Invalid animatoin target (%d) attribute for node: %s.\n", channel->getTargetAttribute(), fbxNode->GetName());
-        }
-        return;
-    }
-}
-
-void decompose(FbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotation, Vector3* translation)
-{
-    FbxAMatrix fbxMatrix;
-    Matrix matrix;
-    FbxTime kTime;
-    kTime.SetMilliSeconds((FbxLongLong)time);
-    fbxMatrix = fbxNode->EvaluateLocalTransform(kTime);
-    copyMatrix(fbxMatrix, matrix);
-    matrix.decompose(scale, rotation, translation);
-}
-
-AnimationChannel* createAnimationChannel(FbxNode* fbxNode, unsigned int targetAttrib, const std::vector<float>& keyTimes, const std::vector<float>& keyValues)
-{
-    AnimationChannel* channel = new AnimationChannel();
-    channel->setTargetId(fbxNode->GetName());
-    channel->setKeyTimes(keyTimes);
-    channel->setKeyValues(keyValues);
-    channel->setInterpolation(AnimationChannel::LINEAR);
-    channel->setTargetAttribute(targetAttrib);
-    return channel;
-}
-
-void addScaleChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime)
-{
-    std::vector<float> keyTimes;
-    std::vector<float> keyValues;
-    Vector3 scale;
-    Quaternion rotation;
-    Vector3 translation;
-
-    decompose(fbxNode, startTime, &scale, &rotation, &translation);
-    keyTimes.push_back(startTime);
-    keyValues.push_back(scale.x);
-    keyValues.push_back(scale.y);
-    keyValues.push_back(scale.z);
-
-    decompose(fbxNode, stopTime, &scale, &rotation, &translation);
-    keyTimes.push_back(stopTime);
-    keyValues.push_back(scale.x);
-    keyValues.push_back(scale.y);
-    keyValues.push_back(scale.z);
-
-    AnimationChannel* channel = createAnimationChannel(fbxNode, Transform::ANIMATE_SCALE, keyTimes, keyValues);
-    animation->add(channel);
-}
-
-void addTranslateChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime)
-{
-    std::vector<float> keyTimes;
-    std::vector<float> keyValues;
-    Vector3 scale;
-    Quaternion rotation;
-    Vector3 translation;
-
-    decompose(fbxNode, startTime, &scale, &rotation, &translation);
-    keyTimes.push_back(startTime);
-    keyValues.push_back(translation.x);
-    keyValues.push_back(translation.y);
-    keyValues.push_back(translation.z);
-
-    decompose(fbxNode, stopTime, &scale, &rotation, &translation);
-    keyTimes.push_back(stopTime);
-    keyValues.push_back(translation.x);
-    keyValues.push_back(translation.y);
-    keyValues.push_back(translation.z);
-
-    AnimationChannel* channel = createAnimationChannel(fbxNode, Transform::ANIMATE_TRANSLATE, keyTimes, keyValues);
-    animation->add(channel);
-}
-
-void copyMatrix(const FbxMatrix& fbxMatrix, float* matrix)
-{
-    int i = 0;
-    for (int row = 0; row < 4; ++row)
-    {
-        for (int col = 0; col < 4; ++col)
-        {
-            matrix[i++] = (float)fbxMatrix.Get(row, col);
-        }
-    }
-}
-
-void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix)
-{
-    int i = 0;
-    for (int row = 0; row < 4; ++row)
-    {
-        for (int col = 0; col < 4; ++col)
-        {
-            matrix.m[i++] = (float)fbxMatrix.Get(row, col);
-        }
-    }
-}
-
-bool isGroupAnimationPossible(FbxScene* fbxScene)
-{
-    FbxNode* rootNode = fbxScene->GetRootNode();
-    if (rootNode)
-    {
-        if (isGroupAnimationPossible(rootNode))
-            return true;
-    }
-    return false;
-}
-
-bool isGroupAnimationPossible(FbxNode* fbxNode)
-{
-    if (fbxNode)
-    {
-        FbxMesh* fbxMesh = fbxNode->GetMesh();
-        if (isGroupAnimationPossible(fbxMesh))
-            return true;
-        const int childCount = fbxNode->GetChildCount();
-        for (int i = 0; i < childCount; ++i)
-        {
-            if (isGroupAnimationPossible(fbxNode->GetChild(i)))
-                return true;
-        }
-    }
-    return false;
-}
-
-bool isGroupAnimationPossible(FbxMesh* fbxMesh)
-{
-    if (fbxMesh)
-    {
-        const int deformerCount = fbxMesh->GetDeformerCount();
-        for (int i = 0; i < deformerCount; ++i)
-        {
-            FbxDeformer* deformer = fbxMesh->GetDeformer(i);
-            if (deformer->GetDeformerType() == FbxDeformer::eSkin)
-            {
-                FbxSkin* fbxSkin = static_cast<FbxSkin*>(deformer);
-                if (fbxSkin)
-                {
-                    return true;
-                }
-            }
-        }
-    }
-    return false;
-}
-
-void generateTangentsAndBinormals(FbxNode* fbxNode, const EncoderArguments& arguments)
-{
-    if (!fbxNode)
-        return;
-    const char* name = fbxNode->GetName();
-    if (name && strlen(name) > 0)
-    {
-        FbxMesh* fbxMesh = fbxNode->GetMesh();
-        if (fbxMesh && arguments.isGenerateTangentBinormalId(std::string(name)))
-        {
-            fbxMesh->GenerateTangentsDataForAllUVSets();
-        }
-    }
-    // visit child nodes
-    const int childCount = fbxNode->GetChildCount();
-    for (int i = 0; i < childCount; ++i)
-    {
-        generateTangentsAndBinormals(fbxNode->GetChild(i), arguments);
-    }
-}
-
 #endif

+ 79 - 0
tools/encoder/src/FBXSceneEncoder.h

@@ -60,6 +60,15 @@ public:
      */
     void write(const std::string& filepath, const EncoderArguments& arguments);
 
+    /**
+     * Writes a material file.
+     * 
+     * @param filepath 
+     * 
+     * @return True if successful; false otherwise.
+     */
+    bool writeMaterial(const std::string& filepath);
+
 private:
 
     /**
@@ -126,6 +135,33 @@ private:
      */
     void loadModel(FbxNode* fbxNode, Node* node);
 
+    /**
+     * Loads materials for each node in the scene.
+     */
+    void loadMaterials(FbxScene* fbxScene);
+
+    /**
+     * Loads the material from the given node.
+     */
+    void loadMaterial(FbxNode* fbxNode);
+
+    void loadMaterialTextures(FbxSurfaceMaterial* fbxMaterial, Material* material);
+
+    void loadMaterialFileTexture(FbxFileTexture* fileTexture, Material* material);
+
+    void loadMaterialUniforms(FbxSurfaceMaterial* fbxMaterial, Material* material);
+
+    /**
+     * Creates a material from an FbxSurfaceMaterial.
+     * 
+     * @param name The name of the new material.
+     * @param fbxMaterial The FBX material to copy from.
+     * @param node The node that the material is being created for.
+     * 
+     * @return The newly created material.
+     */
+    Material* createMaterial(const std::string& name, FbxSurfaceMaterial* fbxMaterial, Node* node);
+
     /**
      * Loads the mesh skin from the given FBX mesh and adds it to the given GamePlay model.
      *
@@ -185,6 +221,34 @@ private:
      */
     void transformNode(FbxNode* fbxNode, Node* node);
 
+    /**
+     * Gets the base material that matches the given id. Returns NULL if not found.
+     */
+    Material* getBaseMaterial(const char* id);
+
+    /**
+     * Finds the base material for the given material.
+     * This will either return a previously loaded base material or 
+     * the base material will be created and returned. (Should never return NULL)
+     */
+    Material* findBaseMaterial(Material* material);
+
+    /**
+     * Finds the gameplay Node that was created from the given FbxNode.
+     * Returns NULL if not found.
+     */
+    Node* findNode(FbxNode* fbxNode);
+
+    /**
+     * Creates a base material with the given name from the given child material.
+     * 
+     * @param baseMaterialName The name of the base material to create.
+     * @param childMaterial The child material that the base material is being created for.
+     * 
+     * @return The newly created base material.
+     */
+    Material* createBaseMaterial(const std::string& baseMaterialName, Material* childMaterial);
+
     /**
      * Recursively triangules the meshes starting from the given node.
      * 
@@ -204,6 +268,21 @@ private:
      */
     std::map<FbxUInt64, Mesh*> _meshes;
 
+    /**
+     * The list of child materials that were loaded.
+     */
+    std::map<std::string, Material*> _materials;
+
+    /**
+     * The list of base materials that the child materials are derived from.
+     */
+    std::map<std::string, Material*> _baseMaterials;
+
+    /**
+     * A map for keeping track of which gameplay Node was created from a given FbxNode.
+     */
+    std::map<FbxNode*, Node*> _nodeMap;
+
     /**
      * The animation that channels should be added to if the user is using the -groupAnimation command line argument. May be NULL.
      */

+ 809 - 0
tools/encoder/src/FBXUtil.cpp

@@ -0,0 +1,809 @@
+#ifdef USE_FBX
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+
+#include "FBXUtil.h"
+#include "Transform.h"
+#include "Vector3.h"
+#include "Vector2.h"
+
+using namespace gameplay;
+using std::string;
+using std::vector;
+using std::map;
+using std::ostringstream;
+
+float getAspectRatio(FbxCamera* fbxCamera)
+{
+    return (float)fbxCamera->FilmAspectRatio.Get();
+    /*
+    FbxCamera::ECameraAspectRatioMode camAspectRatioMode = fbxCamera->GetAspectRatioMode();
+    double aspectX = fbxCamera->AspectWidth.Get();
+    double aspectY = fbxCamera->AspectHeight.Get();
+    double aspectRatio = 1.333333;
+    switch ( camAspectRatioMode)
+    {
+    case FbxCamera::eWINDOW_SIZE:
+        aspectRatio = aspectX / aspectY;
+        break;
+    case FbxCamera::eFIXED_RATIO:
+        aspectRatio = aspectX;
+        break;
+    case FbxCamera::eFIXED_RESOLUTION:
+        aspectRatio = aspectX / aspectY * fbxCamera->GetPixelRatio();
+        break;
+    case FbxCamera::eFIXED_WIDTH:
+        aspectRatio = fbxCamera->GetPixelRatio() / aspectY;
+        break;
+    case FbxCamera::eFIXED_HEIGHT:
+        aspectRatio = fbxCamera->GetPixelRatio() * aspectX;
+        break;
+    default:
+        break;
+    }
+    return (float)aspectRatio;
+    */
+}
+
+inline double vfov(double hfov, double aspect)
+{
+    static const double MATH_PI_180 = 0.01745329251994329576923690768489;
+    static const double MATH_180_PI = 57.295779513082320876798154814105;
+    return (2.0 * atan((aspect) * tan( (hfov * MATH_PI_180) * 0.5)) * MATH_180_PI);
+}
+
+float getFieldOfView(FbxCamera* fbxCamera)
+{
+    double fieldOfViewX = 0.0;
+    double fieldOfViewY = 0.0;
+    double filmHeight = fbxCamera->GetApertureHeight();
+    double filmWidth = fbxCamera->GetApertureWidth() * fbxCamera->GetSqueezeRatio();
+    double apertureRatio = filmHeight / filmWidth;
+    if ( fbxCamera->GetApertureMode() == FbxCamera::eVertical)
+    {
+        fieldOfViewY = fbxCamera->FieldOfView.Get();
+    }
+    else if (fbxCamera->GetApertureMode() == FbxCamera::eHorizontal)
+    {
+        fieldOfViewX = fbxCamera->FieldOfView.Get();
+        fieldOfViewY = vfov( fieldOfViewX, apertureRatio);
+    }
+    else if (fbxCamera->GetApertureMode() == FbxCamera::eFocalLength)
+    {
+        fieldOfViewX = fbxCamera->ComputeFieldOfView(fbxCamera->FocalLength.Get());
+        fieldOfViewY = vfov( fieldOfViewX, apertureRatio);
+    }
+    else if (fbxCamera->GetApertureMode() == FbxCamera::eHorizAndVert)
+    {
+        fieldOfViewY = fbxCamera->FieldOfViewY.Get();
+    }
+    else
+    {
+        fieldOfViewY = 45.0;
+    }
+    return (float)fieldOfViewY;
+}
+
+void loadTextureCoords(FbxMesh* fbxMesh, const FbxGeometryElementUV* uvs, int uvSetIndex, int polyIndex, int posInPoly, int meshVertexIndex, Vertex* vertex)
+{
+    assert(fbxMesh && polyIndex >= 0 && posInPoly >= 0);
+
+    const bool useIndex = uvs->GetReferenceMode() != FbxGeometryElement::eDirect;
+    const int indexCount = useIndex ? uvs->GetIndexArray().GetCount() : 0;
+    int uvIndex = -1;
+
+    switch (uvs->GetMappingMode())
+    {
+    case FbxGeometryElement::eByControlPoint:
+        {
+            // Get the index of the current vertex in control points array
+            int polyVertIndex = fbxMesh->GetPolygonVertex(polyIndex, posInPoly);
+
+            // The UV index depends on the reference mode
+            uvIndex = useIndex ? uvs->GetIndexArray().GetAt(polyVertIndex) : polyVertIndex;
+        }
+        break;
+
+    case FbxGeometryElement::eByPolygonVertex:
+        if (meshVertexIndex < indexCount)
+        {
+            uvIndex = useIndex ? uvs->GetIndexArray().GetAt(meshVertexIndex) : meshVertexIndex;
+        }
+        break;
+
+    default:
+        // Only support eByPolygonVertex and eByControlPoint mappings
+        break;
+    }
+
+    vertex->hasTexCoord[uvSetIndex] = true;
+
+    // Store UV information in vertex
+    if (uvIndex != -1)
+    {
+        FbxVector2 uvValue = uvs->GetDirectArray().GetAt(uvIndex);
+        vertex->texCoord[uvSetIndex].x = (float)uvValue[0];
+        vertex->texCoord[uvSetIndex].y = (float)uvValue[1];
+    }
+}
+
+void loadNormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
+{
+    if (fbxMesh->GetElementNormalCount() > 0)
+    {
+        // Get only the first
+        FbxGeometryElementNormal* normal = fbxMesh->GetElementNormal(0);
+        FbxGeometryElement::EMappingMode mappingMode = normal->GetMappingMode();
+        if (mappingMode == FbxGeometryElement::eByControlPoint)
+        {
+            switch (normal->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(controlPointIndex);
+                    vertex->hasNormal = true;
+                    vertex->normal.x = (float)vec4[0];
+                    vertex->normal.y = (float)vec4[1];
+                    vertex->normal.z = (float)vec4[2];
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = normal->GetIndexArray().GetAt(controlPointIndex);
+                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(id);
+                    vertex->hasNormal = true;
+                    vertex->normal.x = (float)vec4[0];
+                    vertex->normal.y = (float)vec4[1];
+                    vertex->normal.z = (float)vec4[2];
+                }
+                break;
+            default:
+                break;
+            }
+        }
+        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
+        {
+            switch (normal->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(vertexIndex);
+                    vertex->hasNormal = true;
+                    vertex->normal.x = (float)vec4[0];
+                    vertex->normal.y = (float)vec4[1];
+                    vertex->normal.z = (float)vec4[2];
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = normal->GetIndexArray().GetAt(vertexIndex);
+                    FbxVector4 vec4 = normal->GetDirectArray().GetAt(id);
+                    vertex->hasNormal = true;
+                    vertex->normal.x = (float)vec4[0];
+                    vertex->normal.y = (float)vec4[1];
+                    vertex->normal.z = (float)vec4[2];
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+}
+
+void loadTangent(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
+{
+    if (fbxMesh->GetElementTangentCount() > 0)
+    {
+        // Get only the first tangent
+        FbxGeometryElementTangent* tangent = fbxMesh->GetElementTangent(0);
+        FbxGeometryElement::EMappingMode mappingMode = tangent->GetMappingMode();
+        if (mappingMode == FbxGeometryElement::eByControlPoint)
+        {
+            switch (tangent->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(controlPointIndex);
+                    vertex->hasTangent = true;
+                    vertex->tangent.x = (float)vec4[0];
+                    vertex->tangent.y = (float)vec4[1];
+                    vertex->tangent.z = (float)vec4[2];
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = tangent->GetIndexArray().GetAt(controlPointIndex);
+                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(id);
+                    vertex->hasTangent = true;
+                    vertex->tangent.x = (float)vec4[0];
+                    vertex->tangent.y = (float)vec4[1];
+                    vertex->tangent.z = (float)vec4[2];
+                }
+                break;
+            default:
+                break;
+            }
+        }
+        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
+        {
+            switch (tangent->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(vertexIndex);
+                    vertex->hasTangent = true;
+                    vertex->tangent.x = (float)vec4[0];
+                    vertex->tangent.y = (float)vec4[1];
+                    vertex->tangent.z = (float)vec4[2];
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = tangent->GetIndexArray().GetAt(vertexIndex);
+                    FbxVector4 vec4 = tangent->GetDirectArray().GetAt(id);
+                    vertex->hasTangent = true;
+                    vertex->tangent.x = (float)vec4[0];
+                    vertex->tangent.y = (float)vec4[1];
+                    vertex->tangent.z = (float)vec4[2];
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+}
+
+void loadBinormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
+{
+    if (fbxMesh->GetElementBinormalCount() > 0)
+    {
+        // Get only the first binormal.
+        FbxGeometryElementBinormal* binormal = fbxMesh->GetElementBinormal(0);
+        FbxGeometryElement::EMappingMode mappingMode = binormal->GetMappingMode();
+
+        if (mappingMode == FbxGeometryElement::eByControlPoint)
+        {
+            switch (binormal->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(controlPointIndex);
+                    vertex->hasBinormal = true;
+                    vertex->binormal.x = (float)vec4[0];
+                    vertex->binormal.y = (float)vec4[1];
+                    vertex->binormal.z = (float)vec4[2];
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = binormal->GetIndexArray().GetAt(controlPointIndex);
+                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(id);
+                    vertex->hasBinormal = true;
+                    vertex->binormal.x = (float)vec4[0];
+                    vertex->binormal.y = (float)vec4[1];
+                    vertex->binormal.z = (float)vec4[2];
+                }
+                break;
+            default:
+                break;
+            }
+        }
+        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
+        {
+            switch (binormal->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(vertexIndex);
+                    vertex->hasBinormal = true;
+                    vertex->binormal.x = (float)vec4[0];
+                    vertex->binormal.y = (float)vec4[1];
+                    vertex->binormal.z = (float)vec4[2];
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = binormal->GetIndexArray().GetAt(vertexIndex);
+                    FbxVector4 vec4 = binormal->GetDirectArray().GetAt(id);
+                    vertex->hasBinormal = true;
+                    vertex->binormal.x = (float)vec4[0];
+                    vertex->binormal.y = (float)vec4[1];
+                    vertex->binormal.z = (float)vec4[2];
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+}
+
+void loadVertexColor(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex)
+{
+    if (fbxMesh->GetElementVertexColorCount() > 0)
+    {
+        // Get only the first vertex color.
+        FbxGeometryElementVertexColor* vertexColor = fbxMesh->GetElementVertexColor(0);
+        FbxGeometryElement::EMappingMode mappingMode = vertexColor->GetMappingMode();
+        if (mappingMode == FbxGeometryElement::eByControlPoint)
+        {
+            switch (vertexColor->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxColor color = vertexColor->GetDirectArray().GetAt(controlPointIndex);
+
+                    vertex->hasDiffuse = true;
+                    vertex->diffuse.x = (float)color.mRed;
+                    vertex->diffuse.y = (float)color.mGreen;
+                    vertex->diffuse.z = (float)color.mBlue;
+                    vertex->diffuse.w = (float)color.mAlpha;
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = vertexColor->GetIndexArray().GetAt(controlPointIndex);
+                    FbxColor color = vertexColor->GetDirectArray().GetAt(id);
+
+                    vertex->hasDiffuse = true;
+                    vertex->diffuse.x = (float)color.mRed;
+                    vertex->diffuse.y = (float)color.mGreen;
+                    vertex->diffuse.z = (float)color.mBlue;
+                    vertex->diffuse.w = (float)color.mAlpha;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+        else if (mappingMode == FbxGeometryElement::eByPolygonVertex)
+        {
+            switch (vertexColor->GetReferenceMode())
+            {
+            case FbxGeometryElement::eDirect:
+                {
+                    FbxColor color = vertexColor->GetDirectArray().GetAt(vertexIndex);
+
+                    vertex->hasDiffuse = true;
+                    vertex->diffuse.x = (float)color.mRed;
+                    vertex->diffuse.y = (float)color.mGreen;
+                    vertex->diffuse.z = (float)color.mBlue;
+                    vertex->diffuse.w = (float)color.mAlpha;
+                }
+                break;
+            case FbxGeometryElement::eIndexToDirect:
+                {
+                    int id = vertexColor->GetIndexArray().GetAt(vertexIndex);
+                    FbxColor color = vertexColor->GetDirectArray().GetAt(id);
+
+                    vertex->hasDiffuse = true;
+                    vertex->diffuse.x = (float)color.mRed;
+                    vertex->diffuse.y = (float)color.mGreen;
+                    vertex->diffuse.z = (float)color.mBlue;
+                    vertex->diffuse.w = (float)color.mAlpha;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+}
+
+void loadBlendData(const vector<Vector2>& vertexWeights, Vertex* vertex)
+{
+    size_t size = vertexWeights.size();
+
+    if (size >= 1)
+    {
+        vertex->hasWeights= true;
+        vertex->blendIndices.x = vertexWeights[0].x;
+        vertex->blendWeights.x = vertexWeights[0].y;
+    }
+    if (size >= 2)
+    {
+        vertex->blendIndices.y = vertexWeights[1].x;
+        vertex->blendWeights.y = vertexWeights[1].y;
+    }
+    if (size >= 3)
+    {
+        vertex->blendIndices.z = vertexWeights[2].x;
+        vertex->blendWeights.z = vertexWeights[2].y;
+    }
+    if (size >= 4)
+    {
+        vertex->blendIndices.w = vertexWeights[3].x;
+        vertex->blendWeights.w = vertexWeights[3].y;
+    }
+    //vertex->normalizeBlendWeight();
+}
+
+bool loadBlendWeights(FbxMesh* fbxMesh, vector<vector<Vector2> >& weights)
+{
+    assert(fbxMesh);
+    const int vertexCount = fbxMesh->GetControlPointsCount();
+
+    FbxSkin* fbxSkin = NULL;
+    const int deformerCount = fbxMesh->GetDeformerCount();
+    for (int i = 0; i < deformerCount; ++i)
+    {
+        FbxDeformer* deformer = fbxMesh->GetDeformer(i);
+        if (deformer->GetDeformerType() == FbxDeformer::eSkin)
+        {
+            fbxSkin = FbxCast<FbxSkin>(deformer);
+            weights.resize(vertexCount);
+
+            const int clusterCount = fbxSkin->GetClusterCount();
+            for (int j = 0; j < clusterCount; ++j)
+            {
+                FbxCluster* cluster = fbxSkin->GetCluster(j);
+                assert(cluster);
+                const int vertexIndexCount = cluster->GetControlPointIndicesCount();
+                for (int k = 0; k < vertexIndexCount; ++k)
+                {
+                    int index = cluster->GetControlPointIndices()[k];
+                    if (index >= vertexCount)
+                    {
+                        continue;
+                    }
+
+                    double weight = cluster->GetControlPointWeights()[k];
+                    if (weight == 0.0)
+                    {
+                        continue;
+                    }
+                    weights[index].push_back(Vector2((float)j, (float)weight));
+                }
+            }
+            // Only the first skin deformer will be loaded.
+            // There probably won't be more than one.
+            break;
+        }
+    }
+    return fbxSkin != NULL;
+}
+
+void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate)
+{
+    FbxTime start, stop;
+    FbxTimeSpan timeSpan;
+    animCurve->GetTimeInterval(timeSpan);
+    start = timeSpan.GetStart();
+    stop = timeSpan.GetStop();
+    *startTime = std::min(*startTime, (float)start.GetMilliSeconds());
+    *stopTime = std::max(*stopTime, (float)stop.GetMilliSeconds());
+    *frameRate = std::max(*frameRate, (float)stop.GetFrameRate(FbxTime::eDefaultMode));
+}
+
+void appendKeyFrame(FbxNode* fbxNode, AnimationChannel* channel, float time, const Vector3& scale, const Quaternion& rotation, const Vector3& translation)
+{
+    // Write key time
+    channel->getKeyTimes().push_back(time);
+
+    // Write key values
+    vector<float>& keyValues = channel->getKeyValues();
+    switch (channel->getTargetAttribute())
+    {
+        case Transform::ANIMATE_SCALE:
+        {
+            keyValues.push_back(scale.x);
+            keyValues.push_back(scale.y);
+            keyValues.push_back(scale.z);
+        }
+        break;
+
+        case Transform::ANIMATE_SCALE_X:
+        {
+            keyValues.push_back(scale.x);
+        }
+        break;
+
+        case Transform::ANIMATE_SCALE_Y:
+        {
+            keyValues.push_back(scale.y);
+        }
+        break;
+
+        case Transform::ANIMATE_SCALE_Z:
+        {
+            keyValues.push_back(scale.z);
+        }
+        break;
+
+        case Transform::ANIMATE_ROTATE:
+        {
+            keyValues.push_back(rotation.x);
+            keyValues.push_back(rotation.y);
+            keyValues.push_back(rotation.z);
+            keyValues.push_back(rotation.w);
+        }
+        break;
+
+        case Transform::ANIMATE_TRANSLATE:
+        {
+            keyValues.push_back(translation.x);
+            keyValues.push_back(translation.y);
+            keyValues.push_back(translation.z);
+        }
+        break;
+
+        case Transform::ANIMATE_TRANSLATE_X:
+        {
+            keyValues.push_back(translation.x);
+        }
+        break;
+
+        case Transform::ANIMATE_TRANSLATE_Y:
+        {
+            keyValues.push_back(translation.y);
+        }
+        break;
+
+        case Transform::ANIMATE_TRANSLATE_Z:
+        {
+            keyValues.push_back(translation.z);
+        }
+        break;
+
+        case Transform::ANIMATE_ROTATE_TRANSLATE:
+        {
+            keyValues.push_back(rotation.x);
+            keyValues.push_back(rotation.y);
+            keyValues.push_back(rotation.z);
+            keyValues.push_back(rotation.w);
+            keyValues.push_back(translation.x);
+            keyValues.push_back(translation.y);
+            keyValues.push_back(translation.z);
+        }
+        break;
+
+        case Transform::ANIMATE_SCALE_ROTATE_TRANSLATE:
+        {
+            keyValues.push_back(scale.x);
+            keyValues.push_back(scale.y);
+            keyValues.push_back(scale.z);
+            keyValues.push_back(rotation.x);
+            keyValues.push_back(rotation.y);
+            keyValues.push_back(rotation.z);
+            keyValues.push_back(rotation.w);
+            keyValues.push_back(translation.x);
+            keyValues.push_back(translation.y);
+            keyValues.push_back(translation.z);
+        }
+        break;
+
+        case Transform::ANIMATE_SCALE_TRANSLATE:
+        {
+            keyValues.push_back(scale.x);
+            keyValues.push_back(scale.y);
+            keyValues.push_back(scale.z);
+            keyValues.push_back(translation.x);
+            keyValues.push_back(translation.y);
+            keyValues.push_back(translation.z);
+        }
+        break;
+
+        case Transform::ANIMATE_SCALE_ROTATE:
+        {
+            keyValues.push_back(scale.x);
+            keyValues.push_back(scale.y);
+            keyValues.push_back(scale.z);
+            keyValues.push_back(rotation.x);
+            keyValues.push_back(rotation.y);
+            keyValues.push_back(rotation.z);
+            keyValues.push_back(rotation.w);
+        }
+        break;
+
+        default:
+        {
+            LOG(1, "Warning: Invalid animatoin target (%d) attribute for node: %s.\n", channel->getTargetAttribute(), fbxNode->GetName());
+        }
+        return;
+    }
+}
+
+void decompose(FbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotation, Vector3* translation)
+{
+    FbxAMatrix fbxMatrix;
+    Matrix matrix;
+    FbxTime kTime;
+    kTime.SetMilliSeconds((FbxLongLong)time);
+    fbxMatrix = fbxNode->EvaluateLocalTransform(kTime);
+    copyMatrix(fbxMatrix, matrix);
+    matrix.decompose(scale, rotation, translation);
+}
+
+AnimationChannel* createAnimationChannel(FbxNode* fbxNode, unsigned int targetAttrib, const vector<float>& keyTimes, const vector<float>& keyValues)
+{
+    AnimationChannel* channel = new AnimationChannel();
+    channel->setTargetId(fbxNode->GetName());
+    channel->setKeyTimes(keyTimes);
+    channel->setKeyValues(keyValues);
+    channel->setInterpolation(AnimationChannel::LINEAR);
+    channel->setTargetAttribute(targetAttrib);
+    return channel;
+}
+
+void addScaleChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime)
+{
+    vector<float> keyTimes;
+    vector<float> keyValues;
+    Vector3 scale;
+    Quaternion rotation;
+    Vector3 translation;
+
+    decompose(fbxNode, startTime, &scale, &rotation, &translation);
+    keyTimes.push_back(startTime);
+    keyValues.push_back(scale.x);
+    keyValues.push_back(scale.y);
+    keyValues.push_back(scale.z);
+
+    decompose(fbxNode, stopTime, &scale, &rotation, &translation);
+    keyTimes.push_back(stopTime);
+    keyValues.push_back(scale.x);
+    keyValues.push_back(scale.y);
+    keyValues.push_back(scale.z);
+
+    AnimationChannel* channel = createAnimationChannel(fbxNode, Transform::ANIMATE_SCALE, keyTimes, keyValues);
+    animation->add(channel);
+}
+
+void addTranslateChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime)
+{
+    vector<float> keyTimes;
+    vector<float> keyValues;
+    Vector3 scale;
+    Quaternion rotation;
+    Vector3 translation;
+
+    decompose(fbxNode, startTime, &scale, &rotation, &translation);
+    keyTimes.push_back(startTime);
+    keyValues.push_back(translation.x);
+    keyValues.push_back(translation.y);
+    keyValues.push_back(translation.z);
+
+    decompose(fbxNode, stopTime, &scale, &rotation, &translation);
+    keyTimes.push_back(stopTime);
+    keyValues.push_back(translation.x);
+    keyValues.push_back(translation.y);
+    keyValues.push_back(translation.z);
+
+    AnimationChannel* channel = createAnimationChannel(fbxNode, Transform::ANIMATE_TRANSLATE, keyTimes, keyValues);
+    animation->add(channel);
+}
+
+void copyMatrix(const FbxMatrix& fbxMatrix, float* matrix)
+{
+    int i = 0;
+    for (int row = 0; row < 4; ++row)
+    {
+        for (int col = 0; col < 4; ++col)
+        {
+            matrix[i++] = (float)fbxMatrix.Get(row, col);
+        }
+    }
+}
+
+void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix)
+{
+    int i = 0;
+    for (int row = 0; row < 4; ++row)
+    {
+        for (int col = 0; col < 4; ++col)
+        {
+            matrix.m[i++] = (float)fbxMatrix.Get(row, col);
+        }
+    }
+}
+
+bool isGroupAnimationPossible(FbxScene* fbxScene)
+{
+    FbxNode* rootNode = fbxScene->GetRootNode();
+    if (rootNode)
+    {
+        if (isGroupAnimationPossible(rootNode))
+            return true;
+    }
+    return false;
+}
+
+bool isGroupAnimationPossible(FbxNode* fbxNode)
+{
+    if (fbxNode)
+    {
+        FbxMesh* fbxMesh = fbxNode->GetMesh();
+        if (isGroupAnimationPossible(fbxMesh))
+            return true;
+        const int childCount = fbxNode->GetChildCount();
+        for (int i = 0; i < childCount; ++i)
+        {
+            if (isGroupAnimationPossible(fbxNode->GetChild(i)))
+                return true;
+        }
+    }
+    return false;
+}
+
+bool isGroupAnimationPossible(FbxMesh* fbxMesh)
+{
+    if (fbxMesh)
+    {
+        const int deformerCount = fbxMesh->GetDeformerCount();
+        for (int i = 0; i < deformerCount; ++i)
+        {
+            FbxDeformer* deformer = fbxMesh->GetDeformer(i);
+            if (deformer->GetDeformerType() == FbxDeformer::eSkin)
+            {
+                FbxSkin* fbxSkin = FbxCast<FbxSkin>(deformer);
+                if (fbxSkin)
+                {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+bool isBlack(FbxDouble3& fbxDouble)
+{
+    return fbxDouble[0] == 0.0 && fbxDouble[1] == 0.0 && fbxDouble[2] == 0.0;
+}
+
+void generateTangentsAndBinormals(FbxNode* fbxNode, const EncoderArguments& arguments)
+{
+    if (!fbxNode)
+        return;
+    const char* name = fbxNode->GetName();
+    if (name && strlen(name) > 0)
+    {
+        FbxMesh* fbxMesh = fbxNode->GetMesh();
+        if (fbxMesh && arguments.isGenerateTangentBinormalId(string(name)))
+        {
+            fbxMesh->GenerateTangentsDataForAllUVSets();
+        }
+    }
+    // visit child nodes
+    const int childCount = fbxNode->GetChildCount();
+    for (int i = 0; i < childCount; ++i)
+    {
+        generateTangentsAndBinormals(fbxNode->GetChild(i), arguments);
+    }
+}
+
+FbxAnimCurve* getCurve(FbxPropertyT<FbxDouble3>& prop, FbxAnimLayer* animLayer, const char* pChannel)
+{
+#if FBXSDK_VERSION_MAJOR == 2013 && FBXSDK_VERSION_MINOR == 1
+    return prop.GetCurve<FbxAnimCurve>(animLayer, pChannel);
+#else
+    return prop.GetCurve(animLayer, pChannel);
+#endif
+}
+
+std::string toString(const FbxDouble3& fbxDouble)
+{
+    ostringstream stream;
+    stream << fbxDouble[0] << ", " << fbxDouble[1] << ", " << fbxDouble[2];
+    return stream.str();
+}
+
+std::string toString(const FbxDouble3& fbxDouble, double d)
+{
+    ostringstream stream;
+    stream << fbxDouble[0] << ", " << fbxDouble[1] << ", " << fbxDouble[2] << ", " << d;
+    return stream.str();
+}
+
+std::string toString(double value)
+{
+    ostringstream stream;
+    stream << value;
+    return stream.str();
+}
+
+#endif

+ 206 - 0
tools/encoder/src/FBXUtil.h

@@ -0,0 +1,206 @@
+#ifndef FBXUTIL_H_
+#define FBXUTIL_H_
+
+#ifdef USE_FBX
+
+#define FBXSDK_NEW_API
+
+#include <iostream>
+#include <list>
+#include <vector>
+#include <ctime>
+#ifdef WIN32
+    #pragma warning( disable : 4100 )
+    #pragma warning( disable : 4512 )
+#endif
+#include <fbxsdk.h>
+
+#include "Base.h"
+#include "Vertex.h"
+#include "Animation.h"
+#include "AnimationChannel.h"
+#include "EncoderArguments.h"
+
+using namespace gameplay;
+
+/**
+ * Returns the aspect ratio from the given camera.
+ * 
+ * @param fbxCamera The FBX camera to get the aspect ratio from.
+ * 
+ * @return The aspect ratio from the camera.
+ */
+float getAspectRatio(FbxCamera* fbxCamera);
+
+/**
+ * Returns the field of view Y from the given camera.
+ * 
+ * @param fbxCamera The camera to get the fiew of view from.
+ * 
+ * @return The field of view Y.
+ */
+float getFieldOfView(FbxCamera* fbxCamera);
+
+/**
+ * Loads the texture coordinates from given mesh's polygon part into the vertex.
+ * 
+ * @param fbxMesh The mesh to get the polygon from.
+ * @param uvs The UV list to load tex coords from.
+ * @param uvSetIndex The UV set index of the uvs.
+ * @param polyIndex The index of the polygon in the mesh.
+ * @param posInPoly The position of the vertex in the polygon.
+ * @param meshVertexIndex The index of the vertex in the mesh.
+ * @param vertex The vertex to copy the texture coordinates to.
+ */
+void loadTextureCoords(FbxMesh* fbxMesh, const FbxGeometryElementUV* uvs, int uvSetIndex, int polyIndex, int posInPoly, int meshVertexIndex, Vertex* vertex);
+
+/**
+ * Loads the normal from the mesh and adds it to the given vertex.
+ * 
+ * @param fbxMesh The mesh to get the polygon from.
+ * @param vertexIndex The vertex index in the mesh.
+ * @param controlPointIndex The control point index.
+ * @param vertex The vertex to copy to.
+ */
+void loadNormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
+
+/**
+ * Loads the tangent from the mesh and adds it to the given vertex.
+ * 
+ * @param fbxMesh The mesh to load from.
+ * @param vertexIndex The index of the vertex within fbxMesh.
+ * @param controlPointIndex The control point index.
+ * @param vertex The vertex to copy to.
+ */
+void loadTangent(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
+
+/**
+ * Loads the binormal from the mesh and adds it to the given vertex.
+ * 
+ * @param fbxMesh The mesh to load from.
+ * @param vertexIndex The index of the vertex within fbxMesh.
+ * @param controlPointIndex The control point index.
+ * @param vertex The vertex to copy to.
+ */
+void loadBinormal(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
+
+/**
+ * Loads the vertex diffuse color from the mesh and adds it to the given vertex.
+ * 
+ * @param fbxMesh The mesh to load from.
+ * @param vertexIndex The index of the vertex within fbxMesh.
+ * @param controlPointIndex The control point index.
+ * @param vertex The vertex to copy to.
+ */
+void loadVertexColor(FbxMesh* fbxMesh, int vertexIndex, int controlPointIndex, Vertex* vertex);
+
+/**
+ * Loads the blend weight and blend indices data into the vertex.
+ * 
+ * @param vertexWeights List of vertex weights. The x member contains the blendIndices. The y member contains the blendWeights.
+ * @param vertex The vertex to copy the blend data to.
+ */
+void loadBlendData(const std::vector<Vector2>& vertexWeights, Vertex* vertex);
+
+/**
+ * Loads the blend weights and blend indices from the given mesh.
+ * 
+ * Each element of weights is a list of Vector2s where "x" is the blend index and "y" is the blend weight.
+ * 
+ * @param fbxMesh The mesh to load from.
+ * @param weights List of blend weights and blend indices for each vertex.
+ * 
+ * @return True if this mesh has a mesh skin, false otherwise.
+ */
+bool loadBlendWeights(FbxMesh* fbxMesh, std::vector<std::vector<Vector2> >& weights);
+
+/**
+ * Copies from an FBX matrix to a float[16] array.
+ */
+void copyMatrix(const FbxMatrix& fbxMatrix, float* matrix);
+
+/**
+ * Copies from an FBX matrix to a gameplay matrix.
+ */
+void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix);
+
+/**
+ * Finds the min and max start time and stop time of the given animation curve.
+ * 
+ * startTime is updated if the animation curve contains a start time that is less than startTime.
+ * stopTime is updated if the animation curve contains a stop time that is greater than stopTime.
+ * frameRate is updated if the animation curve contains a frame rate that is greater than frameRate.
+ * 
+ * @param animCurve The animation curve to read from.
+ * @param startTime The min start time. (in/out)
+ * @param stopTime The max stop time. (in/out)
+ * @param frameRate The frame rate. (in/out)
+ */
+void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate);
+
+/**
+ * Appends key frame data to the given node for the specified animation target attribute.
+ * 
+ * @param fbxNode The node to get the matrix transform from.
+ * @param channel The aniamtion channel to write values into.
+ * @param time The time of the keyframe.
+ * @param scale The evaluated scale for the keyframe.
+ * @param rotation The evalulated rotation for the keyframe.
+ * @param translation The evalulated translation for the keyframe.
+
+ */
+void appendKeyFrame(FbxNode* fbxNode, AnimationChannel* channel, float time, const Vector3& scale, const Quaternion& rotation, const Vector3& translation);
+
+/**
+ * Decomposes the given node's matrix transform at the given time and copies to scale, rotation and translation.
+ * 
+ * @param fbxNode The node to get the matrix transform from.
+ * @param time The time to get the matrix transform from.
+ * @param scale The scale to copy to.
+ * @param rotation The rotation to copy to.
+ * @param translation The translation to copy to.
+ */
+void decompose(FbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotation, Vector3* translation);
+
+/**
+ * Creates an animation channel that targets the given node and target attribute using the given key times and key values.
+ * 
+ * @param fbxNode The node to target.
+ * @param targetAttrib The attribute type to target.
+ * @param keyTimes The key times for the animation channel.
+ * @param keyValues The key values for the animation channel.
+ * 
+ * @return The newly created animation channel.
+ */
+AnimationChannel* createAnimationChannel(FbxNode* fbxNode, unsigned int targetAttrib, const std::vector<float>& keyTimes, const std::vector<float>& keyValues);
+
+void addScaleChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime);
+
+void addTranslateChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime);
+
+/**
+ * Determines if it is possible to automatically group animations for mesh skins.
+ * 
+ * @param fbxScene The FBX scene to search.
+ * 
+ * @return True if there is at least one mesh skin that has animations that can be grouped.
+ */
+bool isGroupAnimationPossible(FbxScene* fbxScene);
+bool isGroupAnimationPossible(FbxNode* fbxNode);
+bool isGroupAnimationPossible(FbxMesh* fbxMesh);
+
+bool isBlack(FbxDouble3& fbxDouble);
+
+/**
+ * Recursively generates the tangents and binormals for all nodes that were specified in the command line arguments.
+ */
+void generateTangentsAndBinormals(FbxNode* fbxNode, const EncoderArguments& arguments);
+
+FbxAnimCurve* getCurve(FbxPropertyT<FbxDouble3>& prop, FbxAnimLayer* animLayer, const char* pChannel);
+
+std::string toString(const FbxDouble3& fbxDouble);
+std::string toString(const FbxDouble3& fbxDouble, double d);
+std::string toString(double value);
+
+#endif
+#endif

+ 8 - 0
tools/encoder/src/FileIO.cpp

@@ -178,6 +178,14 @@ void writeVectorText(const Vector4& v, FILE* file)
     fprintf(file, "%f %f %f %f\n", v.x, v.y, v.z, v.w);
 }
 
+void writeIndent(unsigned int indentLevel, FILE* file)
+{
+    for (unsigned int i = 0; i < indentLevel; ++i)
+    {
+        fprintf(file, "    ");
+    }
+}
+
 bool promptUserGroupAnimations()
 {
     char buffer[80];

+ 5 - 0
tools/encoder/src/FileIO.h

@@ -137,6 +137,11 @@ void writeVectorBinary(const Vector4& v, FILE* file);
 
 void writeVectorText(const Vector4& v, FILE* file);
 
+/**
+ * Writes a number of white space indentations to the file.
+ */
+void writeIndent(unsigned int indentLevel, FILE* file);
+
 /**
  * Prompts the user if they want to group animations automatically.
  * If the user enters an invalid response, the question is asked again.

+ 5 - 0
tools/encoder/src/GPBFile.cpp

@@ -297,6 +297,11 @@ Animations* GPBFile::getAnimations()
     return &_animations;
 }
 
+unsigned int GPBFile::getLightCount() const
+{
+    return (unsigned int)_lights.size();
+}
+
 void GPBFile::adjust()
 {
     // calculate the ambient color for each scene

+ 5 - 0
tools/encoder/src/GPBFile.h

@@ -97,6 +97,11 @@ public:
 
     Animations* getAnimations();
 
+    /**
+     * Returns the number of lights.
+     */
+    unsigned int getLightCount() const;
+
     /**
      * Adjusts the game play binary file before it is written.
      */

+ 325 - 16
tools/encoder/src/Material.cpp

@@ -1,39 +1,348 @@
-#include "Base.h"
 #include "Material.h"
+#include "FileIO.h"
+#include "StringUtil.h"
 
 namespace gameplay
 {
 
-Material::Material(void) :
-    _effect(NULL)
+using std::string;
+using std::vector;
+using std::map;
+
+Material::Material(const std::string& id) :
+    _parent(NULL), _id(id), _lit(false)
+{
+}
+
+Material::Material(const Material& c) :
+    _parent(c._parent),
+    _id(c._id),
+    _vertexShader(c._vertexShader),
+    _fragmentShader(c._fragmentShader),
+    _defines(c._defines),
+    _uniforms(c._uniforms),
+    _renderStates(c._renderStates)
 {
+    for (vector<Sampler*>::const_iterator it = c._samplers.begin(); it != c._samplers.end(); ++it)
+    {
+        _samplers.push_back(new Sampler(**it));
+    }
 }
 
 Material::~Material(void)
 {
 }
 
-unsigned int Material::getTypeId(void) const
+const string& Material::getId() const
+{
+    return _id;
+}
+
+void Material::setId(const char* id)
+{
+    if (id)
+        _id.assign(id);
+}
+
+Material* Material::getParent() const
+{
+    return _parent;
+}
+
+void Material::setParent(Material* material)
+{
+    if (material)
+        _parent = material;
+}
+
+void Material::addDefine(const string& name)
+{
+    if (!name.empty())
+    {
+        _defines[name] = string();
+    }
+}
+
+bool Material::isDefined(const string& name) const
+{
+    if (!name.empty())
+    {
+        return _defines.find(name) != _defines.end();
+    }
+    return false;
+}
+
+const char* Material::getUniform(const char* name) const
+{
+    map<string, string>::const_iterator it = _uniforms.find(string(name));
+    if (it != _uniforms.end())
+    {
+        return it->second.c_str();
+    }
+    return NULL;
+}
+
+void Material::setUniform(const string& name, const string& value)
+{
+    _uniforms[name] = value;
+}
+
+const char* Material::getRenderState(const char* name) const
+{
+    map<string, string>::const_iterator it = _renderStates.find(string(name));
+    if (it != _renderStates.end())
+    {
+        return it->second.c_str();
+    }
+    return NULL;
+}
+
+void Material::setRenderState(const string& name, const string& value)
+{
+    if (!name.empty())
+        _renderStates[name] = value;
+}
+
+void Material::setVertexShader(const char* path)
+{
+    if (path)
+        _vertexShader.assign(path);
+}
+
+void Material::setFragmentShader(const char* path)
+{
+    if (path)
+        _fragmentShader.assign(path);
+}
+
+Sampler* Material::createSampler(const string& id)
+{
+    Sampler* sampler = new Sampler(id.c_str());
+    sampler->set("mipmap", "true");
+    sampler->set("wrapS", CLAMP);
+    sampler->set("wrapT", CLAMP);
+    sampler->set(MIN_FILTER, LINEAR_MIPMAP_LINEAR);
+    sampler->set(MAG_FILTER, LINEAR);
+    _samplers.push_back(sampler);
+    return sampler;
+}
+
+Sampler* Material::getSampler(const string& id) const
+{
+    for (vector<Sampler*>::const_iterator it = _samplers.begin(); it != _samplers.end(); ++it)
+    {
+        Sampler* sampler = *it;
+        if (sampler->getId() == id)
+        {
+            return sampler;
+        }
+    }
+    return NULL;
+}
+
+bool Material::isTextured() const
+{
+    return !_samplers.empty();
+}
+
+bool Material::isBumped() const
+{
+    return getSampler("u_normalmapTexture") != NULL;
+}
+
+bool Material::isLit() const
+{
+    return _lit;
+}
+
+bool Material::isSpecular() const
 {
-    return MATERIAL_ID;
+    return isDefined(SPECULAR);
 }
-const char* Material::getElementName(void) const
+
+bool Material::isTextureRepeat() const
 {
-    return "Material";
+    return isDefined(TEXTURE_REPEAT);
 }
 
-void Material::writeBinary(FILE* file)
+bool Material::isVertexColor() const
 {
-    Object::writeBinary(file);
-    //write(_parameters, file);
-    //write(_effect, file);
+    return isDefined("VERTEX_COLOR");
 }
-void Material::writeText(FILE* file)
+
+bool Material::isSkinned() const
 {
-    fprintElementStart(file);
-    //fprintfElement(file, "parameters", _parameters);
-    //fprintfElement(file, "effect", _effect);
-    fprintElementEnd(file);
+    return isDefined("SKINNING");
+}
+
+bool Material::isModulateAlpha() const
+{
+    return isDefined("MODULATE_ALPHA");
+}
+
+void Material::setLit(bool value)
+{
+    _lit = value;
+}
+
+void Material::writeMaterial(FILE* file)
+{
+    fprintf(file, "material");
+    if (getId().length() > 0)
+    {
+        fprintf(file, " %s", getId().c_str());
+    }
+    if (_parent)
+    {
+         assert(_parent->getId().length() > 0);
+        fprintf(file, " : %s", _parent->getId().c_str());
+    }
+    fprintf(file, "\n");
+    fprintf(file, "{\n");
+    unsigned int indent = 1;
+
+    writeUniforms(file, indent);
+    writeSamplers(file, indent);
+    writeRenderStates(file, indent);
+    writeTechniqueAndPass(file, indent);
+
+    --indent;
+    writeIndent(indent, file);
+    fprintf(file, "}\n");
+}
+
+void Material::writeDefines(FILE* file, unsigned int& indent)
+{
+    writeIndent(indent, file);
+    fprintf(file, "defines = ");
+    for (map<string, string>::const_iterator it = _defines.begin(); it != _defines.end(); ++it)
+    {
+        if (it != _defines.begin())
+        {
+            fprintf(file, ";");
+        }
+        if (it->second.empty())
+        {
+            fprintf(file, "%s", it->first.c_str());
+        }
+        else
+        {
+            fprintf(file, "%s %s", it->first.c_str(), it->second.c_str());
+        }
+    }
+    fprintf(file, "\n");
+}
+
+void Material::writeUniforms(FILE* file, unsigned int& indent)
+{
+    for (map<string, string>::const_iterator it = _uniforms.begin(); it != _uniforms.end(); ++it)
+    {
+        writeIndent(indent, file);
+        fprintf(file, "%s = %s\n", it->first.c_str(), it->second.c_str());
+    }
+    if (!_uniforms.empty())
+    {
+        writeIndent(indent, file);
+        fprintf(file, "\n");
+    }
+}
+
+void Material::writeSamplers(FILE* file, unsigned int& indent)
+{
+    for (vector<Sampler*>::iterator it = _samplers.begin(); it != _samplers.end(); ++it)
+    {
+        Sampler* sampler = *it;
+        Sampler* parentSampler = NULL;
+        if (_parent)
+        {
+            parentSampler = _parent->getSampler(sampler->getId().c_str());
+        }
+        sampler->writeMaterial(file, indent, parentSampler);
+        fprintf(file, "\n");
+    }
+}
+
+void Material::writeRenderStates(FILE* file, unsigned int& indent)
+{
+    if (_renderStates.empty())
+        return;
+    writeIndent(indent, file);
+    fprintf(file, "renderState\n");
+    writeIndent(indent, file);
+    fprintf(file, "{\n");
+    ++indent;
+    for (map<string, string>::const_iterator it = _renderStates.begin(); it != _renderStates.end(); ++it)
+    {
+        writeIndent(indent, file);
+        fprintf(file, "%s = %s\n", it->first.c_str(), it->second.c_str());
+    }
+    --indent;
+    writeIndent(indent, file);
+    fprintf(file, "}\n");
+    writeIndent(indent, file);
+    fprintf(file, "\n");
+}
+
+void Material::writeTechniqueAndPass(FILE* file, unsigned int& indent)
+{
+    if (!_vertexShader.empty() || !_fragmentShader.empty() || !_defines.empty())
+    {
+        bool techniqueWritten = false;
+
+        if (!_vertexShader.empty() || 
+            !_fragmentShader.empty() || 
+            (!_defines.empty() && (!_parent || _parent->_defines != _defines)))
+        {
+            writeTechniqueOpening(file, indent);
+            techniqueWritten = true;
+        }
+        
+        if (!_vertexShader.empty())
+        {
+            writeIndent(indent, file);
+            fprintf(file, "%s = %s\n", "vertexShader", _vertexShader.c_str());
+        }
+        if (!_fragmentShader.empty())
+        {
+            writeIndent(indent, file);
+            fprintf(file, "%s = %s\n", "fragmentShader", _fragmentShader.c_str());
+        }
+        if (!_defines.empty())
+        {
+            if (!_parent || _parent->_defines != _defines)
+            {
+                writeDefines(file, indent);
+            }
+        }
+
+        if (techniqueWritten)
+        {
+            --indent;
+            writeIndent(indent, file);
+            fprintf(file, "}\n");
+
+            --indent;
+            writeIndent(indent, file);
+            fprintf(file, "}\n");
+        }
+    }
+}
+
+void Material::writeTechniqueOpening(FILE* file, unsigned int& indent)
+{
+    // write the techniques
+    writeIndent(indent, file);
+    fprintf(file, "technique\n");
+    writeIndent(indent, file);
+    fprintf(file, "{\n");
+    ++indent;
+
+    // write the passes
+    writeIndent(indent, file);
+    fprintf(file, "pass \n");
+    writeIndent(indent, file);
+    fprintf(file, "{\n");
+    ++indent;
 }
 
 }

+ 74 - 11
tools/encoder/src/Material.h

@@ -1,35 +1,98 @@
 #ifndef MATERIAL_H_
 #define MATERIAL_H_
 
-#include "Object.h"
-#include "MaterialParameter.h"
-#include "Effect.h"
+#include "Sampler.h"
+#include "Light.h"
+#include "Constants.h"
 
 namespace gameplay
 {
 
-class Material : public Object
+class Material
 {
 public:
 
     /**
      * Constructor.
      */
-    Material(void);
+    Material(const std::string& id);
+
+    Material(const Material&);
 
     /**
      * Destructor.
      */
     virtual ~Material(void);
 
-    virtual unsigned int getTypeId(void) const;
-    virtual const char* getElementName(void) const;
-    virtual void writeBinary(FILE* file);
-    virtual void writeText(FILE* file);
+    /**
+     * Returns this material's id string.
+     */
+    const std::string& getId() const;
+
+    /**
+     * Sets this material's id string.
+     */
+    void setId(const char* id);
+
+    Material* getParent() const;
+    void setParent(Material* material);
+
+    void addDefine(const std::string& name);
+    bool isDefined(const std::string& name) const;
+
+    const char* getUniform(const char* name) const;
+    void setUniform(const std::string& name, const std::string& value);
+
+    const char* getRenderState(const char* name) const;
+    void setRenderState(const std::string& name, const std::string& value);
+
+    void setVertexShader(const char* path);
+    void setFragmentShader(const char* path);
+
+    /**
+     * Creates a sampler and adds it to this material.
+     */
+    Sampler* createSampler(const std::string& id);
+    Sampler* getSampler(const std::string& id) const;
+    
+    bool isTextured() const;
+    bool isBumped() const;
+    bool isLit() const;
+    bool isSpecular() const;
+    bool isTextureRepeat() const;
+    bool isVertexColor() const;
+    bool isSkinned() const;
+    bool isModulateAlpha() const;
+
+    void setLit(bool value);
+
+    /**
+     * Writes this material to the given file.
+     */
+    void writeMaterial(FILE* file);
 
 private:
-    std::list<MaterialParameter> _parameters;
-    Effect* _effect;
+
+    Material& operator=(const Material&); // Hidden copy assignment operator.
+
+    void writeDefines(FILE* file, unsigned int& indent);
+    void writeUniforms(FILE* file, unsigned int& indent);
+    void writeSamplers(FILE* file, unsigned int& indent);
+    void writeRenderStates(FILE* file, unsigned int& indent);
+    void writeTechniqueAndPass(FILE* file, unsigned int& indent);
+    void writeTechniqueOpening(FILE* file, unsigned int& indent);
+
+private:
+    Material* _parent;
+    std::string _id;
+    std::string _vertexShader;
+    std::string _fragmentShader;
+    bool _lit;
+
+    std::map<std::string, std::string> _defines;
+    std::map<std::string, std::string> _uniforms;
+    std::map<std::string, std::string> _renderStates;
+    std::vector<Sampler*> _samplers;
 };
 
 }

+ 11 - 1
tools/encoder/src/Mesh.cpp

@@ -57,7 +57,7 @@ void Mesh::writeBinaryVertices(FILE* file)
     else
     {
         // No vertex data
-        write((unsigned int)0, file);
+        writeZero(file);
     }
 
     // Write bounds
@@ -166,6 +166,16 @@ unsigned int Mesh::getVertexIndex(const Vertex& vertex)
     return it->second;
 }
 
+bool Mesh::hasNormals() const
+{
+    return !vertices.empty() && vertices[0].hasNormal;
+}
+
+bool Mesh::hasVertexColors() const
+{
+    return !vertices.empty() && vertices[0].hasDiffuse;
+}
+
 void Mesh::computeBounds()
 {
     // If we have a Model with a MeshSkin associated with it,

+ 3 - 0
tools/encoder/src/Mesh.h

@@ -60,6 +60,9 @@ public:
 
     unsigned int getVertexIndex(const Vertex& vertex);
 
+    bool hasNormals() const;
+    bool hasVertexColors() const;
+
     void computeBounds();
 
     Model* model;

+ 5 - 0
tools/encoder/src/MeshSkin.cpp

@@ -86,6 +86,11 @@ void MeshSkin::writeText(FILE* file)
     fprintElementEnd(file);
 }
 
+unsigned int MeshSkin::getJointCount() const
+{
+    return _joints.size();
+}
+
 void MeshSkin::setBindShape(const float data[])
 {
     for (int i = 0; i < 16; ++i)

+ 2 - 0
tools/encoder/src/MeshSkin.h

@@ -34,6 +34,8 @@ public:
     virtual void writeBinary(FILE* file);
     virtual void writeText(FILE* file);
 
+    unsigned int getJointCount() const;
+
     void setBindShape(const float data[]);
 
     void setVertexInfluenceCount(unsigned int count);

+ 55 - 5
tools/encoder/src/Model.cpp

@@ -6,7 +6,8 @@ namespace gameplay
 
 Model::Model(void) :
     _mesh(NULL),
-    _meshSkin(NULL)
+    _meshSkin(NULL),
+    _material(NULL)
 {
 }
 
@@ -33,7 +34,7 @@ void Model::writeBinary(FILE* file)
     }
     else
     {
-        write((unsigned int)0, file);
+        writeZero(file);
     }
     // _meshSkin
     // Write one unsigned char to indicate if this model has a skin
@@ -46,9 +47,31 @@ void Model::writeBinary(FILE* file)
     {
         write((bool)false, file); // doesn't have a skin
     }
-    // materials[]
-    writeBinaryObjects(_materials, file);
-
+    // Write the list of materials or zero if there are no materials
+    if (_material && _materials.empty())
+    {
+        write((unsigned int)1, file);
+        write(_material->getId(), file);
+    }
+    else
+    {
+        write((unsigned int)_materials.size(), file);
+        if (_materials.size() > 0)
+        {
+            // Write the material names for each mesh part
+            for (unsigned int i = 0; i < _materials.size(); ++i)
+            {
+                if (Material* mat = _materials[i])
+                {
+                    write(mat->getId(), file);
+                }
+                else
+                {
+                    writeZero(file);
+                }
+            }
+        }
+    }
 }
 
 void Model::writeText(FILE* file)
@@ -64,6 +87,17 @@ void Model::writeText(FILE* file)
     {
         _meshSkin->writeText(file);
     }
+    if (_material)
+    {
+        fprintfElement(file, "material", _material->getId().c_str());
+    }
+    for (unsigned int i = 0; i < _materials.size(); ++i)
+    {
+        if (Material* mat = _materials[i])
+        {
+            fprintfElement(file, "material", mat->getId().c_str());
+        }
+    }
     fprintElementEnd(file);
 }
 
@@ -102,4 +136,20 @@ void Model::setSkin(MeshSkin* skin)
     }
 }
 
+void Model::setMaterial(Material* material, int partIndex)
+{
+    if (partIndex < 0)
+    {
+        _material = material;
+    }
+    else
+    {
+        if ((int)_materials.size() < partIndex + 1)
+        {
+            _materials.resize(partIndex + 1, (Material*)NULL);
+        }
+        _materials[partIndex] = material;
+    }
+}
+
 }

+ 3 - 1
tools/encoder/src/Model.h

@@ -32,12 +32,14 @@ public:
     void setMesh(Mesh* mesh);
     MeshSkin* getSkin();
     void setSkin(MeshSkin* skin);
+    void setMaterial(Material* material, int partIndex = -1);
 
 private:
 
     Mesh* _mesh;
     MeshSkin* _meshSkin;
-    std::list<Material*> _materials;
+    std::vector<Material*> _materials;
+    Material* _material;
 };
 
 }

+ 1 - 1
tools/encoder/src/Object.h

@@ -66,7 +66,7 @@ public:
     virtual void writeText(FILE* file) = 0;
 
     /**
-     * Returns this objects id string.
+     * Returns this object's id string.
      */
     const std::string& getId() const;
 

+ 82 - 0
tools/encoder/src/Sampler.cpp

@@ -0,0 +1,82 @@
+#include "Base.h"
+#include "Sampler.h"
+#include "FileIO.h"
+
+namespace gameplay
+{
+
+using std::string;
+using std::map;
+
+Sampler::Sampler(const char* id) :
+    _id(id)
+{
+}
+
+Sampler::~Sampler(void)
+{
+}
+
+const string& Sampler::getId() const
+{
+    return _id;
+}
+
+const char* Sampler::getString(const string& name)
+{
+    map<string, string>::const_iterator it = props.find(name);
+    if (it != props.end())
+    {
+        return it->second.c_str();
+    }
+    return NULL;
+}
+
+void Sampler::set(const string& name, const string& value)
+{
+    props[name] = value;
+}
+
+void Sampler::writeMaterial(FILE* file, unsigned int indent, Sampler* parent)
+{
+    writeIndent(indent, file);
+    fprintf(file, "sampler %s\n", _id.c_str());
+    writeIndent(indent, file);
+    fprintf(file, "{\n");
+    ++indent;
+
+    const char* relativePath = getString("relativePath");
+    if (relativePath)
+        set("path", relativePath);
+
+    writeProperty(file, "path", indent, parent);
+    writeProperty(file, "mipmap", indent, parent);
+    writeProperty(file, "wrapS", indent, parent);
+    writeProperty(file, "wrapT", indent, parent);
+    writeProperty(file, MIN_FILTER, indent, parent);
+    writeProperty(file, MAG_FILTER, indent, parent);
+    --indent;
+    writeIndent(indent, file);
+    fprintf(file, "}\n");
+}
+
+void Sampler::writeProperty(FILE* file, const string& name, unsigned int indent, Sampler* parent)
+{
+    const char* value = getString(name);
+    if (value != NULL)
+    {
+        const char* parentValue = NULL;
+        // Don't print the property if it is the same as the parent's property
+        if (parent != NULL)
+        {
+            parentValue = parent->getString(name);
+        }
+        if (parentValue == NULL || strcmp(value, parentValue) != 0)
+        {
+            writeIndent(indent, file);
+            fprintf(file, "%s = %s\n", name.c_str(), value);
+        }
+    }
+}
+
+}

+ 50 - 0
tools/encoder/src/Sampler.h

@@ -0,0 +1,50 @@
+#ifndef Sampler_H_
+#define Sampler_H_
+
+#include "Base.h"
+#include "Constants.h"
+
+namespace gameplay
+{
+
+class Sampler
+{
+public:
+
+    /**
+     * Constructor.
+     */
+    Sampler(const char* id);
+
+    /**
+     * Destructor.
+     */
+    virtual ~Sampler(void);
+
+    const std::string& getId() const;
+
+    const char* getString(const std::string& name);
+    void set(const std::string& name, const std::string& value);
+
+    /**
+     * Writes this sampler to a material file.
+     * 
+     * @param file The file pointer.
+     * @param indent The number of indentation levels.
+     * @param parent The parent sampler from this material's parent.
+     */
+    void writeMaterial(FILE* file, unsigned int indent, Sampler* parent = NULL);
+
+private:
+
+    void writeProperty(FILE* file, const std::string& name, unsigned int indent, Sampler* parent = NULL);
+
+private:
+
+    std::string _id;
+    std::map<std::string, std::string> props;
+};
+
+}
+
+#endif

+ 2 - 2
tools/encoder/src/TTFFontEncoder.cpp

@@ -223,8 +223,8 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
             if (penY + rowSize > (int)imageHeight)
             {
                 free(imageBuffer);
-				LOG(1, "Image size exceeded!");
-				return -1;
+                LOG(1, "Image size exceeded!");
+                return -1;
             }
         }