فهرست منبع

Added vertex color support to gameplay-encoder.
Added code for splitting a transform animation into 3 parts (not turned on yet).

Darryl Gough 14 سال پیش
والد
کامیت
9d764e56d4

+ 10 - 0
gameplay-encoder/src/Animation.cpp

@@ -51,6 +51,15 @@ void Animation::add(AnimationChannel* animationChannel)
     _channels.push_back(animationChannel);
 }
 
+void Animation::remove(AnimationChannel* animationChannel)
+{
+    std::vector<AnimationChannel*>::iterator it = std::find(_channels.begin(), _channels.end(), animationChannel);
+    if (it != _channels.end())
+    {
+        _channels.erase(it);
+    }
+}
+
 unsigned int Animation::getAnimationChannelCount() const
 {
     return _channels.size();
@@ -58,6 +67,7 @@ unsigned int Animation::getAnimationChannelCount() const
 
 AnimationChannel* Animation::getAnimationChannel(unsigned int index) const
 {
+    assert(index < _channels.size());
     return _channels[index];
 }
 

+ 17 - 1
gameplay-encoder/src/Animation.h

@@ -26,8 +26,20 @@ public:
     virtual void writeBinary(FILE* file);
     virtual void writeText(FILE* file);
 
+    /**
+     * Adds the given animation channel to this animation.
+     * 
+     * @param animationChannel The animation channel to add.
+     */
     void add(AnimationChannel* animationChannel);
 
+    /**
+     * Removes the given animation channel from this animation.
+     * 
+     * @param animationChannel The animation channel to remove.
+     */
+    void remove(AnimationChannel* animationChannel);
+
     /**
      * Returns the number of animation channels contained in this animation.
      * 
@@ -36,7 +48,11 @@ public:
     unsigned int getAnimationChannelCount() const;
 
     /**
-     * Returns the specified animation channel.
+     * Returns the animation channel at the given index.
+     * 
+     * @param index The index of the animation channel to get.
+     * 
+     * @return The pointer to the animation channel or NULL if not found.
      */
     AnimationChannel* getAnimationChannel(unsigned int index) const;
 

+ 19 - 14
gameplay-encoder/src/AnimationChannel.cpp

@@ -43,7 +43,7 @@ void AnimationChannel::writeText(FILE* file)
 {
     fprintElementStart(file);
     fprintfElement(file, "targetId", _targetId);
-    fprintfElement(file, "targetAttrib", _targetAttrib);
+    fprintf(file, "<%s>%u %s</%s>\n", "targetAttrib", _targetAttrib, Transform::getPropertyString(_targetAttrib), "targetAttrib");
     fprintfElement(file, "%f ", "keytimes", _keytimes);
     fprintfElement(file, "%f ", "values", _keyValues);
     fprintfElement(file, "%f ", "tangentsIn", _tangentsIn);
@@ -130,27 +130,29 @@ void AnimationChannel::setInterpolations(const std::vector<unsigned int>& values
 
 void AnimationChannel::removeDuplicates()
 {
-    if (_targetAttrib == Transform::ANIMATE_SCALE_ROTATE_TRANSLATE)
+    size_t propSize = Transform::getPropertySize(_targetAttrib);
+
+    if (propSize > 1 && !_interpolations.empty() && _interpolations[0] == LINEAR)
     {
         size_t prevIndex = 0;
 
         std::vector<float>::iterator prevStart = _keyValues.begin();
-        std::vector<float>::iterator prevEnd   = _keyValues.begin() + 9;
+        std::vector<float>::iterator prevEnd = prevStart + propSize - 1;
         
         size_t i = 1;
         for (i = 1; i < _keytimes.size(); ++i)
         {
-            std::vector<float>::iterator start = _keyValues.begin() + i * 10;
-            std::vector<float>::iterator end = _keyValues.begin() + (i * 10 + 9);
+            std::vector<float>::iterator start = _keyValues.begin() + i * propSize;
+            std::vector<float>::iterator end = start + propSize - 1;
 
-            if (!equal(prevStart, prevEnd, start))
+            if (!equal(prevStart, prevEnd, start) || i == _keytimes.size() - 1)
             {
                 if (i - prevIndex > 2)
                 {
-                    deleteRange(prevIndex+1, i);
+                    deleteRange(prevIndex+1, i, propSize);
                     i = prevIndex;
-                    prevStart = _keyValues.begin() + i * 10;
-                    prevEnd = _keyValues.begin() + (i * 10 + 9);
+                    prevStart = _keyValues.begin() + i * propSize;
+                    prevEnd = prevStart + propSize - 1;
                 }
                 else
                 {
@@ -162,7 +164,7 @@ void AnimationChannel::removeDuplicates()
         }
         if (i - 1 - prevIndex >= 2)
         {
-            deleteRange(prevIndex+1, i);
+            deleteRange(prevIndex+1, i, propSize);
         }
     }
 }
@@ -270,13 +272,14 @@ unsigned int AnimationChannel::getInterpolationType(const char* str)
     return value;
 }
 
-void AnimationChannel::deleteRange(size_t begin, size_t end)
+void AnimationChannel::deleteRange(size_t begin, size_t end, size_t propSize)
 {
+    assert(end > begin);
     // delete range
     printf("delete %lu to %lu\n", begin, end - 1);
 
-    std::vector<float>::iterator a = _keyValues.begin() + begin * 10;
-    std::vector<float>::iterator b = _keyValues.begin() + end * 10;
+    std::vector<float>::iterator a = _keyValues.begin() + begin * propSize;
+    std::vector<float>::iterator b = _keyValues.begin() + end * propSize;
     _keyValues.erase(a, b);
 
     a = _keytimes.begin() + begin;
@@ -286,9 +289,11 @@ void AnimationChannel::deleteRange(size_t begin, size_t end)
     if (_interpolations.size() > 1)
     {
         std::vector<unsigned int>::iterator a = _interpolations.begin() + begin;
-        std::vector<unsigned int>::iterator b = _interpolations.begin() + end * 10;
+        std::vector<unsigned int>::iterator b = _interpolations.begin() + end * propSize;
         _interpolations.erase(a, b);
     }
+
+    // TODO: also remove key frames from _tangentsIn and _tangentsOut once other curve types are supported.
 }
 
 }

+ 12 - 1
gameplay-encoder/src/AnimationChannel.h

@@ -60,6 +60,9 @@ public:
     const std::vector<float>& getTangentsOut() const;
     const std::vector<unsigned int>& getInterpolationTypes() const;
 
+    /**
+     * Removes duplicate key frames from the animation channel.
+     */
     void removeDuplicates();
 
     void convertToQuaternion();
@@ -77,7 +80,15 @@ public:
 
 private:
 
-    void deleteRange(size_t begin, size_t end);
+    /**
+     * Deletes all key frames from key time index begin to key time index end (exclusive).
+     * 
+     * @param begin The start index to delete.
+     * @param end The index to delete up to but not including.
+     * @param propSize The size of the animation propery to delete. Example: Translate(x,y,z) is size 3.
+     */
+    void deleteRange(size_t begin, size_t end, size_t propSize);
+
 private:
 
     std::string _targetId;

+ 8 - 0
gameplay-encoder/src/Base.h

@@ -78,6 +78,14 @@ void fillArray(float values[], float value, size_t length);
 
 #define ISZERO(x) (fabs(x) < 0.000001f)
 
+// Object deletion macro
+#define SAFE_DELETE(x) \
+    if (x) \
+    { \
+        delete x; \
+        x = NULL; \
+    }
+
 #ifdef NDEBUG
 #define DEBUGPRINT(x)
 #define DEBUGPRINT_VARG(x, ...)

+ 71 - 38
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -1240,9 +1240,9 @@ void DAESceneEncoder::loadSkeleton(domInstance_controller::domSkeleton* skeleton
     for (std::vector<std::string>::const_iterator i = jointNames.begin(); i != jointNames.end(); i++)
     {
         Object* obj = _gamePlayFile.getFromRefTable(*i);
-        if (obj)
+        if (obj && obj->getTypeId() == Object::NODE_ID)
         {
-            Node* node = (Node*)obj;
+            Node* node = static_cast<Node*>(obj);
             _joints.push_back(node);
         }
     }
@@ -1673,10 +1673,9 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
             {
                 maxOffset = offset;
             }
-            int type = polygonInputs[k]->type;
-
             unsigned int polyIndex = (unsigned int) polyInts.get(poly + offset);
-            switch (type)
+
+            switch (polygonInputs[k]->type)
             {
             case POSITION:
                 vertex = Vertex(); // TODO
@@ -1707,16 +1706,52 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
                 vertex.normal.z = (float)source.get(polyIndex * 3 + 2);
                 break;
 
-            // TODO: Handle reading of per-vertex colors.
-            // HOW do we know how many color components to read?
-            // We must examine the Collada input accessor and read the stride/count to verify this - not ONLY for Color, but we should be doing this for ALL components (i.e. Position, Normal, etc).
-//            case Color:
-//                vertex.hasColor = true;
-//                vertex.Diffuse.R = (float)source.get(polyIndex * 3);
-//                vertex.Diffuse.G = (float)source.get(polyIndex * 3 + 1);
-//                vertex.Diffuse.B = (float)source.get(polyIndex * 3 + 2);
-//                vertex.Diffuse.A = (float)source.get(polyIndex * 3 + 3);
-//                break;
+            // TODO: We must examine the Collada input accessor and read the stride/count to verify this - not ONLY for Color, but we should be doing this for ALL components (i.e. Position, Normal, etc).
+            case COLOR:
+            {
+                domAccessor* accessor = polygonInputs[k]->accessor;
+                if (accessor)
+                {
+                    vertex.hasDiffuse = true;
+                    vertex.diffuse.w = 1.0f;
+                    unsigned int stride = (unsigned int)polygonInputs[k]->accessor->getStride();
+                    unsigned int index = polyIndex * stride;
+
+                    const domParam_Array& paramArray = accessor->getParam_array();
+                    const size_t paramArrayCount = paramArray.getCount();
+
+                    for (size_t i = 0; i < paramArrayCount; ++i)
+                    {
+                        const domParamRef& param = paramArray.get(i);
+                        const char* name = param->getName();
+                        if (name)
+                        {
+                            switch (name[0])
+                            {
+                            case 'r':
+                            case 'R':
+                                vertex.diffuse.x = (float)source.get(index + i); // red
+                                break;
+                            case 'g':
+                            case 'G':
+                                vertex.diffuse.y = (float)source.get(index + i); // green
+                                break;
+                            case 'b':
+                            case 'B':
+                                vertex.diffuse.z = (float)source.get(index + i); // blue
+                                break;
+                            case 'a':
+                            case 'A':
+                                vertex.diffuse.w = (float)source.get(index + i); // alpha
+                                break;
+                            default:
+                                break;
+                            }
+                        }
+                    }
+                }
+                break;
+            }
 
             case TANGENT:
                 vertex.hasTangent = true;
@@ -1786,7 +1821,7 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
     }
     
     bool hasNormals = mesh->vertices[0].hasNormal;
-    bool hasColors = mesh->vertices[0].hasColor;
+    bool hasDiffuses = mesh->vertices[0].hasDiffuse;
     bool hasTangents = mesh->vertices[0].hasTangent;
     bool hasBinormals = mesh->vertices[0].hasBinormal;
     bool hasTexCoords = mesh->vertices[0].hasTexCoord;
@@ -1796,38 +1831,38 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
     // It should be the same order as how the Vertex data is written.
 
     // Position
-    mesh->addVetexAttribute(POSITION, 3);
+    mesh->addVetexAttribute(POSITION, Vertex::POSITION_COUNT);
     
     // Normals
     if (hasNormals)
     {
-        mesh->addVetexAttribute(NORMAL, 3);
+        mesh->addVetexAttribute(NORMAL, Vertex::NORMAL_COUNT);
     }
     // Tangents
     if (hasTangents)
     {
-        mesh->addVetexAttribute(TANGENT, 3);
+        mesh->addVetexAttribute(TANGENT, Vertex::TANGENT_COUNT);
     }
     // Binormals
     if (hasBinormals)
     {
-        mesh->addVetexAttribute(BINORMAL, 3);
+        mesh->addVetexAttribute(BINORMAL, Vertex::BINORMAL_COUNT);
     }
     // Texture Coordinates
     if (hasTexCoords)
     {
-        mesh->addVetexAttribute(TEXCOORD0, 2);
+        mesh->addVetexAttribute(TEXCOORD0, Vertex::TEXCOORD_COUNT);
     }
     // Diffuse Color
-    if (hasColors)
+    if (hasDiffuses)
     {
-        mesh->addVetexAttribute(COLOR, 3);
+        mesh->addVetexAttribute(COLOR, Vertex::DIFFUSE_COUNT);
     }
     // Skinning BlendWeights BlendIndices
-    if (hasWeights /*_vertexBlendWeights && _vertexBlendIndices*/)
+    if (hasWeights)
     {
-        mesh->addVetexAttribute(BLENDWEIGHTS, 4);
-        mesh->addVetexAttribute(BLENDINDICES, 4);
+        mesh->addVetexAttribute(BLENDWEIGHTS, Vertex::BLEND_WEIGHTS_COUNT);
+        mesh->addVetexAttribute(BLENDINDICES, Vertex::BLEND_INDICES_COUNT);
     }
 
     _gamePlayFile.addMesh(mesh);
@@ -1846,7 +1881,6 @@ void DAESceneEncoder::warning(const char* message)
 
 int DAESceneEncoder::getVertexUsageType(const std::string& semantic)
 {
-    int type = -1;
     if (semantic.length() > 0)
     {
         switch (semantic[0])
@@ -1854,48 +1888,47 @@ int DAESceneEncoder::getVertexUsageType(const std::string& semantic)
         case 'P':
             if (equals(semantic, "POSITION"))
             {
-                type = POSITION;
+                return POSITION;
             }
-            break;
         case 'N':
             if (equals(semantic, "NORMAL"))
             {
-                type = NORMAL;
+                return NORMAL;
             }
         case 'C':
             if (equals(semantic, "COLOR"))
             {
-                type = COLOR;
+                return COLOR;
             }
         case 'T':
             if (equals(semantic, "TANGENT"))
             {
-                type = TANGENT;
+                return TANGENT;
             }
             else if (equals(semantic, "TEXCOORD"))
             {
-                type = TEXCOORD0;
+                return TEXCOORD0;
             }
             else if (equals(semantic, "TEXTANGENT"))
             {
                 // Treat TEXTANGENT as TANGENT
-                type = TANGENT;
+                return TANGENT;
             }
             else if (equals(semantic, "TEXBINORMAL"))
             {
                 // Treat TEXBINORMAL as BINORMAL
-                type = BINORMAL;
+                return BINORMAL;
             }
         case 'B':
             if (equals(semantic, "BINORMAL"))
             {
-                type = BINORMAL;
+                return BINORMAL;
             }
         default:
-            break;
+            return -1;
         }
     }
-    return type;
+    return -1;
 }
 
 DAESceneEncoder::DAEPolygonInput::DAEPolygonInput(void) :

+ 88 - 39
gameplay-encoder/src/FBXSceneEncoder.cpp

@@ -63,6 +63,15 @@ void loadTangent(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
  */
 void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, 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 vertex The vertex to copy to.
+ */
+void loadVertexColor(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
+
 /**
  * Loads the blend weight and blend indices data into the vertex.
  * 
@@ -778,7 +787,7 @@ Mesh* FBXSceneEncoder::loadMesh(KFbxMesh* fbxMesh)
             loadNormal(fbxMesh, vertexIndex, &vertex);
             loadTangent(fbxMesh, vertexIndex, &vertex);
             loadBinormal(fbxMesh, vertexIndex, &vertex);
-            // TODO: loadDiffuseColors
+            loadVertexColor(fbxMesh, vertexIndex, &vertex);
 
             if (hasSkin)
             {
@@ -819,39 +828,39 @@ Mesh* FBXSceneEncoder::loadMesh(KFbxMesh* fbxMesh)
     // It should be the same order as how the Vertex data is written.
 
     // Position
-    mesh->addVetexAttribute(POSITION, 3);
+    mesh->addVetexAttribute(POSITION, Vertex::POSITION_COUNT);
 
     const Vertex& vertex = mesh->vertices[0];
     // Normals
     if (vertex.hasNormal)
     {
-        mesh->addVetexAttribute(NORMAL, 3);
+        mesh->addVetexAttribute(NORMAL, Vertex::NORMAL_COUNT);
     }
     // Tangents
     if (vertex.hasTangent)
     {
-        mesh->addVetexAttribute(TANGENT, 3);
+        mesh->addVetexAttribute(TANGENT, Vertex::TANGENT_COUNT);
     }
     // Binormals
     if (vertex.hasBinormal)
     {
-        mesh->addVetexAttribute(BINORMAL, 3);
+        mesh->addVetexAttribute(BINORMAL, Vertex::BINORMAL_COUNT);
     }
     // Texture Coordinates
     if (vertex.hasTexCoord)
     {
-        mesh->addVetexAttribute(TEXCOORD0, 2);
+        mesh->addVetexAttribute(TEXCOORD0, Vertex::TEXCOORD_COUNT);
     }
     // Diffuse Color
-    if (vertex.hasColor)
+    if (vertex.hasDiffuse)
     {
-        mesh->addVetexAttribute(COLOR, 3);
+        mesh->addVetexAttribute(COLOR, Vertex::DIFFUSE_COUNT);
     }
     // Skinning BlendWeights BlendIndices
     if (vertex.hasWeights)
     {
-        mesh->addVetexAttribute(BLENDWEIGHTS, 4);
-        mesh->addVetexAttribute(BLENDINDICES, 4);
+        mesh->addVetexAttribute(BLENDWEIGHTS, Vertex::BLEND_WEIGHTS_COUNT);
+        mesh->addVetexAttribute(BLENDINDICES, Vertex::BLEND_INDICES_COUNT);
     }
 
     _gamePlayFile.addMesh(mesh);
@@ -974,23 +983,23 @@ void loadTextureCoords(KFbxMesh* fbxMesh, int polyIndex, int posInPoly, Vertex*
     if (fbxMesh->GetElementUVCount() > 0)
     {
         // Get only the first UV coordinates.
-        KFbxGeometryElementUV* leUV = fbxMesh->GetElementUV(0);
-        switch (leUV->GetMappingMode())
+        KFbxGeometryElementUV* uv = fbxMesh->GetElementUV(0);
+        switch (uv->GetMappingMode())
         {
         case KFbxGeometryElement::eBY_CONTROL_POINT:
-            switch (leUV->GetReferenceMode())
+            switch (uv->GetReferenceMode())
             {
             case KFbxGeometryElement::eDIRECT:
                 vertex->hasTexCoord = true;
-                vertex->texCoord.x = (float)leUV->GetDirectArray().GetAt(polyIndex)[0];
-                vertex->texCoord.y = (float)leUV->GetDirectArray().GetAt(polyIndex)[1];
+                vertex->texCoord.x = (float)uv->GetDirectArray().GetAt(polyIndex)[0];
+                vertex->texCoord.y = (float)uv->GetDirectArray().GetAt(polyIndex)[1];
                 break;
             case KFbxGeometryElement::eINDEX_TO_DIRECT:
                 {
-                    int id = leUV->GetIndexArray().GetAt(polyIndex);
+                    int id = uv->GetIndexArray().GetAt(polyIndex);
                     vertex->hasTexCoord = true;
-                    vertex->texCoord.x = (float)leUV->GetDirectArray().GetAt(id)[0];
-                    vertex->texCoord.y = (float)leUV->GetDirectArray().GetAt(id)[1];
+                    vertex->texCoord.x = (float)uv->GetDirectArray().GetAt(id)[0];
+                    vertex->texCoord.y = (float)uv->GetDirectArray().GetAt(id)[1];
                 }
                 break;
             default:
@@ -1000,13 +1009,13 @@ void loadTextureCoords(KFbxMesh* fbxMesh, int polyIndex, int posInPoly, Vertex*
         case KFbxGeometryElement::eBY_POLYGON_VERTEX:
             {
                 int lTextureUVIndex = fbxMesh->GetTextureUVIndex(polyIndex, posInPoly);
-                switch (leUV->GetReferenceMode())
+                switch (uv->GetReferenceMode())
                 {
                 case KFbxGeometryElement::eDIRECT:
                 case KFbxGeometryElement::eINDEX_TO_DIRECT:
                     vertex->hasTexCoord = true;
-                    vertex->texCoord.x = (float)leUV->GetDirectArray().GetAt(lTextureUVIndex)[0];
-                    vertex->texCoord.y = (float)leUV->GetDirectArray().GetAt(lTextureUVIndex)[1];
+                    vertex->texCoord.x = (float)uv->GetDirectArray().GetAt(lTextureUVIndex)[0];
+                    vertex->texCoord.y = (float)uv->GetDirectArray().GetAt(lTextureUVIndex)[1];
                     break;
                 default:
                     break;
@@ -1024,14 +1033,14 @@ void loadNormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
     if (fbxMesh->GetElementNormalCount() > 0)
     {
         // Get only the first
-        KFbxGeometryElementNormal* leNormal = fbxMesh->GetElementNormal(0);
-        if (leNormal->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
+        KFbxGeometryElementNormal* normal = fbxMesh->GetElementNormal(0);
+        if (normal->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
         {
-            switch (leNormal->GetReferenceMode())
+            switch (normal->GetReferenceMode())
             {
             case KFbxGeometryElement::eDIRECT:
                 {
-                    KFbxVector4 vec4 = leNormal->GetDirectArray().GetAt(vertexIndex);
+                    KFbxVector4 vec4 = normal->GetDirectArray().GetAt(vertexIndex);
                     vertex->hasNormal = true;
                     vertex->normal.x = (float)vec4[0];
                     vertex->normal.y = (float)vec4[1];
@@ -1040,8 +1049,8 @@ void loadNormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
                 break;
             case KFbxGeometryElement::eINDEX_TO_DIRECT:
                 {
-                    int id = leNormal->GetIndexArray().GetAt(vertexIndex);
-                    KFbxVector4 vec4 = leNormal->GetDirectArray().GetAt(id);
+                    int id = normal->GetIndexArray().GetAt(vertexIndex);
+                    KFbxVector4 vec4 = normal->GetDirectArray().GetAt(id);
                     vertex->hasNormal = true;
                     vertex->normal.x = (float)vec4[0];
                     vertex->normal.y = (float)vec4[1];
@@ -1060,14 +1069,14 @@ void loadTangent(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
     if (fbxMesh->GetElementTangentCount() > 0)
     {
         // Get only the first tangent
-        KFbxGeometryElementTangent* leTangent = fbxMesh->GetElementTangent(0);
-        if (leTangent->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
+        KFbxGeometryElementTangent* tangent = fbxMesh->GetElementTangent(0);
+        if (tangent->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
         {
-            switch (leTangent->GetReferenceMode())
+            switch (tangent->GetReferenceMode())
             {
             case KFbxGeometryElement::eDIRECT:
                 {
-                    KFbxVector4 vec4 = leTangent->GetDirectArray().GetAt(vertexIndex);
+                    KFbxVector4 vec4 = tangent->GetDirectArray().GetAt(vertexIndex);
                     vertex->hasTangent = true;
                     vertex->tangent.x = (float)vec4[0];
                     vertex->tangent.y = (float)vec4[1];
@@ -1076,8 +1085,8 @@ void loadTangent(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
                 break;
             case KFbxGeometryElement::eINDEX_TO_DIRECT:
                 {
-                    int id = leTangent->GetIndexArray().GetAt(vertexIndex);
-                    KFbxVector4 vec4 = leTangent->GetDirectArray().GetAt(id);
+                    int id = tangent->GetIndexArray().GetAt(vertexIndex);
+                    KFbxVector4 vec4 = tangent->GetDirectArray().GetAt(id);
                     vertex->hasTangent = true;
                     vertex->tangent.x = (float)vec4[0];
                     vertex->tangent.y = (float)vec4[1];
@@ -1096,14 +1105,14 @@ void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
     if (fbxMesh->GetElementBinormalCount() > 0)
     {
         // Get only the first binormal.
-        KFbxGeometryElementBinormal* leBinormal = fbxMesh->GetElementBinormal(0);
-        if (leBinormal->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
+        KFbxGeometryElementBinormal* binormal = fbxMesh->GetElementBinormal(0);
+        if (binormal->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
         {
-            switch (leBinormal->GetReferenceMode())
+            switch (binormal->GetReferenceMode())
             {
             case KFbxGeometryElement::eDIRECT:
                 {
-                    KFbxVector4 vec4 = leBinormal->GetDirectArray().GetAt(vertexIndex);
+                    KFbxVector4 vec4 = binormal->GetDirectArray().GetAt(vertexIndex);
                     vertex->hasBinormal = true;
                     vertex->binormal.x = (float)vec4[0];
                     vertex->binormal.y = (float)vec4[1];
@@ -1112,8 +1121,8 @@ void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
                 break;
             case KFbxGeometryElement::eINDEX_TO_DIRECT:
                 {
-                    int id = leBinormal->GetIndexArray().GetAt(vertexIndex);
-                    KFbxVector4 vec4 = leBinormal->GetDirectArray().GetAt(id);
+                    int id = binormal->GetIndexArray().GetAt(vertexIndex);
+                    KFbxVector4 vec4 = binormal->GetDirectArray().GetAt(id);
                     vertex->hasBinormal = true;
                     vertex->binormal.x = (float)vec4[0];
                     vertex->binormal.y = (float)vec4[1];
@@ -1127,6 +1136,46 @@ void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
     }
 }
 
+void loadVertexColor(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex)
+{
+    if (fbxMesh->GetElementVertexColorCount() > 0)
+    {
+        // Get only the first vertex color.
+        KFbxGeometryElementVertexColor* vertexColor = fbxMesh->GetElementVertexColor(0);
+        if (vertexColor->GetMappingMode() == KFbxGeometryElement::eBY_POLYGON_VERTEX)
+        {
+            switch (vertexColor->GetReferenceMode())
+            {
+            case KFbxGeometryElement::eDIRECT:
+                {
+                    KFbxColor 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 KFbxGeometryElement::eINDEX_TO_DIRECT:
+                {
+                    int id = vertexColor->GetIndexArray().GetAt(vertexIndex);
+                    KFbxColor 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();

+ 119 - 0
gameplay-encoder/src/GPBFile.cpp

@@ -1,11 +1,19 @@
 #include "Base.h"
 #include "GPBFile.h"
+#include "Transform.h"
+
+#define EPSILON 1.2e-7f;
 
 namespace gameplay
 {
 
 static GPBFile* __instance = NULL;
 
+/**
+ * Returns true if the given value is close to one.
+ */
+static bool isAlmostOne(float value);
+
 GPBFile::GPBFile(void)
     : _file(NULL), _animationsAdded(false)
 {
@@ -245,6 +253,9 @@ void GPBFile::adjust()
         }
     }
 
+    // try to convert joint transform animations into rotation animations
+    //optimizeTransformAnimations(); // TODO: Fix bounding sphere before re-enabling this
+
     // TODO:
     // remove ambient _lights
     // for each node
@@ -259,4 +270,112 @@ void GPBFile::adjust()
     //   This can be merged into one animation. Same for scale animations.
 }
 
+void GPBFile::optimizeTransformAnimations()
+{
+    const unsigned int animationCount = _animations.getAnimationCount();
+    for (unsigned int animationIndex = 0; animationIndex < animationCount; ++animationIndex)
+    {
+        Animation* animation = _animations.getAnimation(animationIndex);
+        assert(animation);
+        const int channelCount = animation->getAnimationChannelCount();
+        // loop backwards because we will be adding and removing channels
+        for (int channelIndex = channelCount -1; channelIndex >= 0 ; --channelIndex)
+        {
+            AnimationChannel* channel = animation->getAnimationChannel(channelIndex);
+            assert(channel);
+            // get target node
+            const Object* obj = _refTable.get(channel->getTargetId());
+            if (obj && obj->getTypeId() == Object::NODE_ID)
+            {
+                const Node* node = static_cast<const Node*>(obj);
+                if (node->isJoint() && channel->getTargetAttribute() == Transform::ANIMATE_SCALE_ROTATE_TRANSLATE)
+                {
+                    decomposeTransformAnimationChannel(animation, channel);
+
+                    animation->remove(channel);
+                    SAFE_DELETE(channel);
+                }
+            }
+        }
+    }
+}
+
+
+void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const AnimationChannel* channel)
+{
+    const std::vector<float>& keyTimes = channel->getKeyTimes();
+    const std::vector<float>& keyValues = channel->getKeyValues();
+    const size_t keyTimesSize = keyTimes.size();
+    const size_t keyValuesSize = keyValues.size();
+
+    std::vector<float> scaleKeyValues;
+    std::vector<float> rotateKeyValues;
+    std::vector<float> translateKeyValues;
+                    
+    scaleKeyValues.reserve(keyTimesSize * 3);
+    rotateKeyValues.reserve(keyTimesSize * 4);
+    translateKeyValues.reserve(keyTimesSize * 3);
+
+    for (size_t kv = 0; kv < keyValuesSize; kv += 10)
+    {
+        scaleKeyValues.push_back(keyValues[kv]);
+        scaleKeyValues.push_back(keyValues[kv+1]);
+        scaleKeyValues.push_back(keyValues[kv+2]);
+
+        rotateKeyValues.push_back(keyValues[kv+3]);
+        rotateKeyValues.push_back(keyValues[kv+4]);
+        rotateKeyValues.push_back(keyValues[kv+5]);
+        rotateKeyValues.push_back(keyValues[kv+6]);
+
+        translateKeyValues.push_back(keyValues[kv+7]);
+        translateKeyValues.push_back(keyValues[kv+8]);
+        translateKeyValues.push_back(keyValues[kv+9]);
+    }
+
+    // replace transform animation channel with translate, rotate and scale animation channels
+
+    // Don't add the scale channel if all the key values are close to 1.0
+    size_t oneCount = (size_t)std::count_if(scaleKeyValues.begin(), scaleKeyValues.end(), isAlmostOne);
+    if (scaleKeyValues.size() != oneCount)
+    {
+        AnimationChannel* scaleChannel = new AnimationChannel();
+        scaleChannel->setTargetId(channel->getTargetId());
+        scaleChannel->setKeyTimes(channel->getKeyTimes());
+        scaleChannel->setTangentsIn(channel->getTangentsIn());
+        scaleChannel->setTangentsOut(channel->getTangentsOut());
+        scaleChannel->setInterpolations(channel->getInterpolationTypes());
+        scaleChannel->setTargetAttribute(Transform::ANIMATE_SCALE);
+        scaleChannel->setKeyValues(scaleKeyValues);
+        scaleChannel->removeDuplicates();
+        animation->add(scaleChannel);
+    }
+
+    AnimationChannel* rotateChannel = new AnimationChannel();
+    rotateChannel->setTargetId(channel->getTargetId());
+    rotateChannel->setKeyTimes(channel->getKeyTimes());
+    rotateChannel->setTangentsIn(channel->getTangentsIn());
+    rotateChannel->setTangentsOut(channel->getTangentsOut());
+    rotateChannel->setInterpolations(channel->getInterpolationTypes());
+    rotateChannel->setTargetAttribute(Transform::ANIMATE_ROTATE);
+    rotateChannel->setKeyValues(rotateKeyValues);
+    rotateChannel->removeDuplicates();
+    animation->add(rotateChannel);
+
+    AnimationChannel* translateChannel = new AnimationChannel();
+    translateChannel->setTargetId(channel->getTargetId());
+    translateChannel->setKeyTimes(channel->getKeyTimes());
+    translateChannel->setTangentsIn(channel->getTangentsIn());
+    translateChannel->setTangentsOut(channel->getTangentsOut());
+    translateChannel->setInterpolations(channel->getInterpolationTypes());
+    translateChannel->setTargetAttribute(Transform::ANIMATE_TRANSLATE);
+    translateChannel->setKeyValues(translateKeyValues);
+    translateChannel->removeDuplicates();
+    animation->add(translateChannel);
+}
+
+static bool isAlmostOne(float value)
+{
+    return std::abs(value - 1.0f) < EPSILON;
+}
+
 }

+ 11 - 0
gameplay-encoder/src/GPBFile.h

@@ -98,6 +98,17 @@ public:
      */
     void adjust();
 
+private:
+    void optimizeTransformAnimations();
+
+    /**
+     * Decomposes an ANIMATE_SCALE_ROTATE_TRANSLATE channel into 3 new channels. (Scale, Rotate and Translate)
+     * 
+     * @param animation The animation that the channel belongs to.
+     * @param channel The animation channel to decompose.
+     */
+    void decomposeTransformAnimationChannel(Animation* animation, const AnimationChannel* channel);
+
 private:
 
     FILE* _file;

+ 2 - 2
gameplay-encoder/src/Node.cpp

@@ -296,7 +296,7 @@ void Node::setIsJoint(bool value)
     _joint = value;
 }
 
-bool Node::isJoint()
+bool Node::isJoint() const
 {
     return _joint;
 }
@@ -324,7 +324,7 @@ Node* Node::getFirstCameraNode() const
 {
     if (hasCamera())
     {
-        return (Node*)this;
+        return const_cast<Node*>(this);
     }
     for (Node* node = getFirstChild(); node != NULL; node = node->getNextSibling())
     {

+ 1 - 1
gameplay-encoder/src/Node.h

@@ -155,7 +155,7 @@ public:
     /**
      * Returns true if this is a joint node.
      */
-    bool isJoint();
+    bool isJoint() const;
 
     Node* getFirstCameraNode() const;
 

+ 89 - 0
gameplay-encoder/src/Transform.cpp

@@ -1,2 +1,91 @@
 #include "Base.h"
 #include "Transform.h"
+
+namespace gameplay
+{
+
+const char* Transform::getPropertyString(unsigned int prop)
+{
+    switch (prop)
+    {
+        case ANIMATE_SCALE:
+            return "ANIMATE_SCALE";
+        case ANIMATE_SCALE_X:
+            return "ANIMATE_SCALE_X";
+        case ANIMATE_SCALE_Y: 
+            return "ANIMATE_SCALE_Y";
+        case ANIMATE_SCALE_Z:
+            return "ANIMATE_SCALE_Z";
+        case ANIMATE_SCALE_XY: 
+            return "ANIMATE_SCALE_XY";
+        case ANIMATE_SCALE_XZ: 
+            return "ANIMATE_SCALE_XZ";
+        case ANIMATE_SCALE_YZ:
+            return "ANIMATE_SCALE_YZ";
+        case ANIMATE_ROTATE:
+            return "ANIMATE_ROTATE";
+        case ANIMATE_TRANSLATE: 
+            return "ANIMATE_TRANSLATE";
+        case ANIMATE_TRANSLATE_X:
+            return "ANIMATE_TRANSLATE_X";
+        case ANIMATE_TRANSLATE_Y: 
+            return "ANIMATE_TRANSLATE_Y";
+        case ANIMATE_TRANSLATE_Z: 
+            return "ANIMATE_TRANSLATE_Z";
+        case ANIMATE_TRANSLATE_XY: 
+            return "ANIMATE_TRANSLATE_XY";
+        case ANIMATE_TRANSLATE_XZ: 
+            return "ANIMATE_TRANSLATE_XZ";
+        case ANIMATE_TRANSLATE_YZ: 
+            return "ANIMATE_TRANSLATE_YZ";
+        case ANIMATE_ROTATE_TRANSLATE: 
+            return "ANIMATE_ROTATE_TRANSLATE";
+        case ANIMATE_SCALE_ROTATE_TRANSLATE: 
+            return "ANIMATE_SCALE_ROTATE_TRANSLATE";
+        case ANIMATE_ROTATE_X: 
+            return "ANIMATE_ROTATE_X";
+        case ANIMATE_ROTATE_Y: 
+            return "ANIMATE_ROTATE_Y";
+        case ANIMATE_ROTATE_Z: 
+            return "ANIMATE_ROTATE_Z";
+        default:
+            return "";
+    }
+}
+
+unsigned int Transform::getPropertySize(unsigned int prop)
+{
+    switch (prop)
+    {
+        case ANIMATE_SCALE_ROTATE_TRANSLATE: 
+            return 10;
+        case ANIMATE_ROTATE_TRANSLATE: 
+            return 7;
+        case ANIMATE_ROTATE:
+            return 4;
+        case ANIMATE_SCALE:
+        case ANIMATE_TRANSLATE: 
+            return 3;   
+        case ANIMATE_SCALE_XY: 
+        case ANIMATE_SCALE_XZ: 
+        case ANIMATE_SCALE_YZ:
+        case ANIMATE_TRANSLATE_XY: 
+        case ANIMATE_TRANSLATE_XZ: 
+        case ANIMATE_TRANSLATE_YZ: 
+            return 2;
+        case ANIMATE_SCALE_X:
+        case ANIMATE_SCALE_Y:
+        case ANIMATE_SCALE_Z:
+        case ANIMATE_TRANSLATE_X:
+        case ANIMATE_TRANSLATE_Y:
+        case ANIMATE_TRANSLATE_Z:
+        case ANIMATE_ROTATE_X: 
+        case ANIMATE_ROTATE_Y: 
+        case ANIMATE_ROTATE_Z: 
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+}

+ 11 - 0
gameplay-encoder/src/Transform.h

@@ -50,6 +50,17 @@ public:
         ANIMATE_ROTATE_Y = 19,
         ANIMATE_ROTATE_Z = 20
     };
+
+    /**
+     * Returns the string representation of the given TransformProperty.
+     */
+    static const char* getPropertyString(unsigned int prop);
+
+    /**
+     * Returns the number of floats for the given property or zero if not a valid property.
+     */
+    static unsigned int getPropertySize(unsigned int prop);
+
 };
 
 }

+ 18 - 12
gameplay-encoder/src/Vertex.cpp

@@ -5,7 +5,7 @@ namespace gameplay
 {
 
 Vertex::Vertex(void)
-    : hasNormal(false), hasTangent(false), hasBinormal(false), hasTexCoord(false), hasColor(false), hasWeights(false)
+    : hasNormal(false), hasTangent(false), hasBinormal(false), hasTexCoord(false), hasDiffuse(false), hasWeights(false)
 {
 }
 
@@ -15,17 +15,19 @@ Vertex::~Vertex(void)
 
 unsigned int Vertex::byteSize() const
 {
-    unsigned int count = 3;
+    unsigned int count = POSITION_COUNT;
     if (hasNormal)
-        count += 3;
+        count += NORMAL_COUNT;
     if (hasTangent)
-        count += 3;
+        count += TANGENT_COUNT;
     if (hasBinormal)
-        count += 3;
+        count += BINORMAL_COUNT;
     if (hasTexCoord)
-        count += 2;
+        count += TEXCOORD_COUNT;
     if (hasWeights)
-        count += 8;
+        count += BLEND_WEIGHTS_COUNT + BLEND_INDICES_COUNT;
+    if (hasDiffuse)
+        count += DIFFUSE_COUNT;
     return count * sizeof(float);
 }
 
@@ -48,11 +50,10 @@ void Vertex::writeBinary(FILE* file) const
     {
         writeVectorBinary(texCoord, file);
     }
-    // TODO add vertex color?
-    //if (hasColor)
-    //{
-    //    writeVectorBinary(color, file);
-    //}
+    if (hasDiffuse)
+    {
+        writeVectorBinary(diffuse, file);
+    }
     if (hasWeights)
     {
         writeVectorBinary(blendWeights, file);
@@ -84,6 +85,11 @@ void Vertex::writeText(FILE* file) const
         write("// texCoord\n", file);
         writeVectorText(texCoord, file);
     }
+    if (hasDiffuse)
+    {
+        write("// diffuse\n", file);
+        writeVectorText(diffuse, file);
+    }
     if (hasWeights)
     {
         write("// blendWeights\n", file);

+ 21 - 7
gameplay-encoder/src/Vertex.h

@@ -13,6 +13,15 @@ class Vertex
 {
 public:
 
+    static const unsigned int POSITION_COUNT = 3;
+    static const unsigned int NORMAL_COUNT = 3;
+    static const unsigned int TANGENT_COUNT = 3;
+    static const unsigned int BINORMAL_COUNT = 3;
+    static const unsigned int TEXCOORD_COUNT = 2;
+    static const unsigned int DIFFUSE_COUNT = 4;
+    static const unsigned int BLEND_WEIGHTS_COUNT = 4;
+    static const unsigned int BLEND_INDICES_COUNT = 4;
+
     /**
      * Constructor.
      */
@@ -28,11 +37,12 @@ public:
     Vector3 tangent;
     Vector3 binormal;
     Vector2 texCoord;
+    Vector4 diffuse;
 
     Vector4 blendWeights;
     Vector4 blendIndices;
 
-    bool hasNormal, hasTangent, hasBinormal, hasTexCoord, hasColor, hasWeights;
+    bool hasNormal, hasTangent, hasBinormal, hasTexCoord, hasDiffuse, hasWeights;
 
     inline bool operator<(const Vertex& v) const
     {
@@ -46,15 +56,19 @@ public:
                     {
                         if (texCoord == v.texCoord)
                         {
-                            if (blendWeights == v.blendWeights)
+                            if (diffuse == v.diffuse)
                             {
-                                if (blendIndices == v.blendIndices)
+                                if (blendWeights == v.blendWeights)
                                 {
-                                    return false;
+                                    if (blendIndices == v.blendIndices)
+                                    {
+                                        return false;
+                                    }
+                                    return blendIndices < v.blendIndices;
                                 }
-                                return blendIndices < v.blendIndices;
+                                return blendWeights < v.blendWeights;
                             }
-                            return blendWeights < v.blendWeights;
+                            return diffuse < v.diffuse;
                         }
                         return texCoord < v.texCoord;
                     }
@@ -70,7 +84,7 @@ public:
     inline bool operator==(const Vertex& v) const
     {
         return position==v.position && normal==v.normal && tangent==v.tangent && binormal==v.binormal && texCoord==v.texCoord &&
-            blendWeights==v.blendWeights && blendIndices==v.blendIndices;
+            diffuse==v.diffuse && blendWeights==v.blendWeights && blendIndices==v.blendIndices;
     }
 
     /**