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

Merge branch 'next' of https://github.com/blackberry/GamePlay into next

Conflicts:
	bin/win32/gameplay-encoder.exe
Darryl Gough 13 лет назад
Родитель
Сommit
b0c8c70e7d
34 измененных файлов с 763 добавлено и 439 удалено
  1. 12 63
      gameplay-encoder/src/AnimationChannel.cpp
  2. 5 8
      gameplay-encoder/src/AnimationChannel.h
  3. 10 8
      gameplay-encoder/src/Base.h
  4. 24 34
      gameplay-encoder/src/DAESceneEncoder.cpp
  5. 0 3
      gameplay-encoder/src/DAESceneEncoder.h
  6. 57 24
      gameplay-encoder/src/EncoderArguments.cpp
  7. 2 0
      gameplay-encoder/src/EncoderArguments.h
  8. 260 103
      gameplay-encoder/src/FBXSceneEncoder.cpp
  9. 0 10
      gameplay-encoder/src/FBXSceneEncoder.h
  10. 94 32
      gameplay-encoder/src/GPBFile.cpp
  11. 7 2
      gameplay-encoder/src/GPBFile.h
  12. 12 12
      gameplay-encoder/src/Heightmap.cpp
  13. 1 1
      gameplay-encoder/src/Light.cpp
  14. 2 0
      gameplay-encoder/src/Mesh.cpp
  15. 14 14
      gameplay-encoder/src/MeshSkin.cpp
  16. 9 9
      gameplay-encoder/src/TTFFontEncoder.cpp
  17. 11 32
      gameplay-encoder/src/Transform.cpp
  18. 10 9
      gameplay-encoder/src/Transform.h
  19. 4 4
      gameplay-encoder/src/main.cpp
  20. 1 1
      gameplay/src/AnimationClip.cpp
  21. 6 6
      gameplay/src/AnimationValue.cpp
  22. 10 10
      gameplay/src/AnimationValue.h
  23. 0 1
      gameplay/src/Bundle.cpp
  24. 3 12
      gameplay/src/MaterialParameter.cpp
  25. 31 27
      gameplay/src/Transform.cpp
  26. 10 0
      gameplay/src/Transform.h
  27. 52 14
      gameplay/src/lua/lua_AnimationValue.cpp
  28. 2 0
      gameplay/src/lua/lua_AnimationValue.h
  29. 36 0
      gameplay/src/lua/lua_Joint.cpp
  30. 2 0
      gameplay/src/lua/lua_Joint.h
  31. 36 0
      gameplay/src/lua/lua_Node.cpp
  32. 2 0
      gameplay/src/lua/lua_Node.h
  33. 36 0
      gameplay/src/lua/lua_Transform.cpp
  34. 2 0
      gameplay/src/lua/lua_Transform.h

+ 12 - 63
gameplay-encoder/src/AnimationChannel.cpp

@@ -68,27 +68,27 @@ unsigned int AnimationChannel::getTargetAttribute() const
     return _targetAttrib;
 }
 
-const std::vector<float>& AnimationChannel::getKeyValues() const
+std::vector<float>& AnimationChannel::getKeyValues()
 {
     return _keyValues;
 }
 
-const std::vector<float>& AnimationChannel::getKeyTimes() const
+std::vector<float>& AnimationChannel::getKeyTimes()
 {
     return _keytimes;
 }
 
-const std::vector<float>& AnimationChannel::getTangentsIn() const
+std::vector<float>& AnimationChannel::getTangentsIn()
 {
     return _tangentsIn;
 }
 
-const std::vector<float>& AnimationChannel::getTangentsOut() const
+std::vector<float>& AnimationChannel::getTangentsOut()
 {
     return _tangentsOut;
 }
 
-const std::vector<unsigned int>& AnimationChannel::getInterpolationTypes() const
+std::vector<unsigned int>& AnimationChannel::getInterpolationTypes()
 {
     return _interpolations;
 }
@@ -130,6 +130,10 @@ void AnimationChannel::setInterpolations(const std::vector<unsigned int>& values
 
 void AnimationChannel::removeDuplicates()
 {
+    LOG(3, "      Removing duplicates for channel with target attribute: %d.\n", _targetAttrib);
+
+    int startCount = _keytimes.size();
+
     size_t propSize = Transform::getPropertySize(_targetAttrib);
 
     if (propSize > 1 && !_interpolations.empty() && _interpolations[0] == LINEAR)
@@ -167,64 +171,8 @@ void AnimationChannel::removeDuplicates()
             deleteRange(prevIndex+1, i, propSize);
         }
     }
-}
-
-void AnimationChannel::convertToQuaternion()
-{
-    if (_targetAttrib == Transform::ANIMATE_ROTATE_X ||
-        _targetAttrib == Transform::ANIMATE_ROTATE_Y ||
-        _targetAttrib == Transform::ANIMATE_ROTATE_Z)
-    {
-        std::vector<float> newKeyValues;
-        newKeyValues.resize(_keyValues.size() * 4);
-        const size_t count = _keyValues.size();
-
-        float x = _targetAttrib == Transform::ANIMATE_ROTATE_X ? 1.0f : 0.0f;
-        float y = _targetAttrib == Transform::ANIMATE_ROTATE_Y ? 1.0f : 0.0f;
-        float z = _targetAttrib == Transform::ANIMATE_ROTATE_Z ? 1.0f : 0.0f;
-        for (size_t i = 0; i < count; ++i)
-        {
-            size_t j = i << 2;
-            newKeyValues[j] = x;
-            newKeyValues[j+1] = y;
-            newKeyValues[j+2] = z;
-            newKeyValues[j+3] = _keyValues[i];
-        }
-        setKeyValues(newKeyValues);
-        setTargetAttribute(Transform::ANIMATE_ROTATE);
-    }
-}
 
-void AnimationChannel::convertToTransform()
-{
-    if (_targetAttrib == Transform::ANIMATE_ROTATE_X ||
-        _targetAttrib == Transform::ANIMATE_ROTATE_Y ||
-        _targetAttrib == Transform::ANIMATE_ROTATE_Z)
-    {
-        std::vector<float> newKeyValues;
-        newKeyValues.resize(_keyValues.size() * 10);
-        const size_t count = _keyValues.size();
-
-        float x = _targetAttrib == Transform::ANIMATE_ROTATE_X ? 1.0f : 0.0f;
-        float y = _targetAttrib == Transform::ANIMATE_ROTATE_Y ? 1.0f : 0.0f;
-        float z = _targetAttrib == Transform::ANIMATE_ROTATE_Z ? 1.0f : 0.0f;
-        for (size_t i = 0; i < count; ++i)
-        {
-            size_t j = i << 2;
-            newKeyValues[j+0] = 1.0f;
-            newKeyValues[j+1] = 1.0f;
-            newKeyValues[j+2] = 1.0f;
-            newKeyValues[j+3] = x;
-            newKeyValues[j+4] = y;
-            newKeyValues[j+5] = z;
-            newKeyValues[j+6] = _keyValues[i];
-            newKeyValues[j+7] = 0.0f;
-            newKeyValues[j+8] = 0.0f;
-            newKeyValues[j+9] = 0.0f;
-        }
-        setKeyValues(newKeyValues);
-        setTargetAttribute(Transform::ANIMATE_SCALE_ROTATE_TRANSLATE);
-    }
+    LOG(3, "      Removed %d duplicate keyframes from channel.\n", startCount- _keytimes.size());
 }
 
 unsigned int AnimationChannel::getInterpolationType(const char* str)
@@ -275,8 +223,9 @@ unsigned int AnimationChannel::getInterpolationType(const char* str)
 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);
+    LOG(4, "        delete %lu to %lu\n", begin, end - 1);
 
     std::vector<float>::iterator a = _keyValues.begin() + begin * propSize;
     std::vector<float>::iterator b = _keyValues.begin() + end * propSize;

+ 5 - 8
gameplay-encoder/src/AnimationChannel.h

@@ -54,20 +54,17 @@ public:
     void setInterpolations(const std::vector<unsigned int>& values);
 
     unsigned int getTargetAttribute() const;
-    const std::vector<float>& getKeyValues() const;
-    const std::vector<float>& getKeyTimes() const;
-    const std::vector<float>& getTangentsIn() const;
-    const std::vector<float>& getTangentsOut() const;
-    const std::vector<unsigned int>& getInterpolationTypes() const;
+    std::vector<float>& getKeyValues();
+    std::vector<float>& getKeyTimes();
+    std::vector<float>& getTangentsIn();
+    std::vector<float>& getTangentsOut();
+    std::vector<unsigned int>& getInterpolationTypes();
 
     /**
      * Removes duplicate key frames from the animation channel.
      */
     void removeDuplicates();
 
-    void convertToQuaternion();
-    void convertToTransform();
-
     /**
      * Returns the interpolation type value for the given string or zero if not valid.
      * Example: "LINEAR" returns AnimationChannel::LINEAR

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

@@ -86,7 +86,8 @@ void fillArray(float values[], float value, size_t length);
  */
 std::string getBaseName(const std::string& filepath);
 
-#define ISZERO(x) (fabs(x) < 0.000001f)
+#define ISZERO(x) (fabs(x) < MATH_EPSILON)
+#define ISONE(x) ((x - 1.0f) < MATH_EPSILON)
 
 // Object deletion macro
 #define SAFE_DELETE(x) \
@@ -96,13 +97,14 @@ std::string getBaseName(const std::string& filepath);
         x = NULL; \
     }
 
-#ifdef NDEBUG
-#define DEBUGPRINT(x)
-#define DEBUGPRINT_VARG(x, ...)
-#else
-#define DEBUGPRINT(x)  printf(x)
-#define DEBUGPRINT_VARG(x, ...) printf(x, __VA_ARGS__)
-#endif
+extern int __logVerbosity;
+
+// Logging macro (level is verbosity level, 1-4).
+#define LOG(level, ...) \
+    { \
+        if (level <= __logVerbosity) \
+            printf(__VA_ARGS__); \
+    }
 
 }
 

+ 24 - 34
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -63,7 +63,7 @@ void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLL
             // Ask the user if they want to group animations automatically.
             if (promptUserGroupAnimations())
             {
-                printf("Grouping animations...\n");
+                LOG(2, "Grouping animations...\n");
 
                 DAEOptimizer optimizer(dom);
                 begin();
@@ -87,7 +87,7 @@ void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLL
     {
         if (!_collada->writeTo(arguments.getFilePath(), arguments.getDAEOutputPath()))
         {
-            fprintf(stderr,"Error: COLLADA failed to write the dom for file: %s\n", arguments.getDAEOutputPath().c_str());
+            LOG(1, "Error: COLLADA failed to write the dom for file: %s\n", arguments.getDAEOutputPath().c_str());
         }
     }
 }
@@ -268,7 +268,7 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
     end("Open file");
     if (!_dom)
     {
-        fprintf(stderr,"Error: COLLADA failed to open file: %s\n", filepath.c_str());
+        LOG(1, "Error: COLLADA failed to open file: %s\n", filepath.c_str());
         if (_collada)
         {
             delete _collada;
@@ -314,23 +314,23 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
                     }
                     else
                     {
-                        fprintf(stderr,"COLLADA File loaded to the dom, but failed to load node %s.\n", nodeId);
+                        LOG(1, "COLLADA File loaded to the dom, but failed to load node %s.\n", nodeId);
                     }
                 }
                 else
                 {
-                    fprintf(stderr,"COLLADA File loaded to the dom, but node was not found with node ID %s.\n", nodeId);
+                    LOG(1, "COLLADA File loaded to the dom, but node was not found with node ID %s.\n", nodeId);
                 }
             }
         }
         else
         {
-             fprintf(stderr,"COLLADA File loaded to the dom, but query for the dom assets failed.\n");
+             LOG(1, "COLLADA File loaded to the dom, but query for the dom assets failed.\n");
         }
     }
     else
     {
-        fprintf(stderr, "COLLADA File loaded to the dom, but missing <visual_scene>.\n");
+        LOG(1, "COLLADA File loaded to the dom, but missing <visual_scene>.\n");
     }
     
     // The animations should be loaded last
@@ -350,20 +350,20 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
         {
             std::string path = outputFilePath.substr(0, pos);
             path.append(".xml");
-            fprintf(stderr, "Saving debug file: %s\n", path.c_str());
+            LOG(1, "Saving debug file: %s\n", path.c_str());
             if (!_gamePlayFile.saveText(path))
             {
-                fprintf(stderr,"Error writing text file: %s\n", path.c_str());
+                LOG(1, "Error writing text file: %s\n", path.c_str());
             }
         }
     }
     else
     {
-        fprintf(stderr, "Saving binary file: %s\n", outputFilePath.c_str());
+        LOG(1, "Saving binary file: %s\n", outputFilePath.c_str());
         begin();
         if (!_gamePlayFile.saveBinary(outputFilePath))
         {
-            fprintf(stderr,"Error writing binary file: %s\n", outputFilePath.c_str());
+            LOG(1, "Error writing binary file: %s\n", outputFilePath.c_str());
         }
         end("save binary");
     }
@@ -569,7 +569,7 @@ bool DAESceneEncoder::loadTarget(const domChannelRef& channelRef, AnimationChann
             daeInt type = attributeElement->typeID();
             if (type == domRotate::ID())
             {
-                printf(TRANSFORM_WARNING_FORMAT, targetId, "Rotate", TRANSFORM_MESSAGE);
+                LOG(1, TRANSFORM_WARNING_FORMAT, targetId, "Rotate", TRANSFORM_MESSAGE);
                 return false;
                 /*
                 // <rotate>
@@ -610,7 +610,7 @@ bool DAESceneEncoder::loadTarget(const domChannelRef& channelRef, AnimationChann
             }
             else if (type == domScale::ID())
             {
-                printf(TRANSFORM_WARNING_FORMAT, targetId, "Scale", TRANSFORM_MESSAGE);
+                LOG(1, TRANSFORM_WARNING_FORMAT, targetId, "Scale", TRANSFORM_MESSAGE);
                 return false;
                 /*
                 // <scale>
@@ -635,7 +635,7 @@ bool DAESceneEncoder::loadTarget(const domChannelRef& channelRef, AnimationChann
             }
             else if (type == domTranslate::ID())
             {
-                printf(TRANSFORM_WARNING_FORMAT, targetId, "Translate", TRANSFORM_MESSAGE);
+                LOG(1, TRANSFORM_WARNING_FORMAT, targetId, "Translate", TRANSFORM_MESSAGE);
                 return false;
                 /*
                 // <translate>
@@ -719,7 +719,7 @@ void DAESceneEncoder::end(const char* str)
 {
     #ifdef ENCODER_PRINT_TIME
     clock_t time = clock() - _begin;
-    fprintf(stderr,"%5d %s\n", time, str);
+    LOG(1, "%5d %s\n", time, str);
     #endif
 }
 
@@ -936,10 +936,10 @@ void DAESceneEncoder::calcTransform(domNode* domNode, Matrix& dstTransform)
                 break;
             }
             case COLLADA_TYPE::SKEW:
-                warning("Skew transform found but not supported.");
+                LOG(1, "Warning: Skew transform found but not supported.\n");
                 break;
             case COLLADA_TYPE::LOOKAT:
-                warning("Lookat transform found but not supported.");
+                LOG(1, "Warning: Lookat transform found but not supported.\n");
                 break;
             default:
                 break;
@@ -1024,7 +1024,7 @@ void DAESceneEncoder::loadGeometryInstance(const domNode* n, Node* node)
         }
         else
         {
-            warning(std::string("Failed to resolve geometry url: ") + geometryURI.getURI());
+            LOG(1, "Failed to resolve geometry url: %s\n", geometryURI.getURI());
         }
     }
 }
@@ -1399,7 +1399,7 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
     // Make sure we have some joints
     if (jointCount == 0)
     {
-        warning("No joints found for skin: ");
+        LOG(1, "Warning: No joints found for skin: %s\n", skinElement->getID());
         return NULL;
     }
 
@@ -1572,7 +1572,7 @@ Model* DAESceneEncoder::loadGeometry(const domGeometry* geometry, const domBind_
     const domMesh* meshElement = geometry->getMesh();
     if (meshElement == NULL)
     {
-        warning(std::string("No mesh found for geometry: ") + geometry->getId());
+        LOG(1, "Warning: No mesh found for geometry: %s\n", geometry->getId());
         return NULL;
     }
 
@@ -1599,7 +1599,7 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
     // Ensure the data is exported as triangles.
     if (trianglesArrayCount == 0)
     {
-        warning(std::string("Geometry mesh has no triangles: ") + geometryId);
+        LOG(1, "Warning: Geometry mesh has no triangles: %s\n", geometryId.c_str());
         return NULL;
     }
 
@@ -1650,7 +1650,7 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
                         int type = getVertexUsageType(semantic);
                         if (type == -1)
                         {
-                            warning(std::string("Vertex semantic (") + semantic + ") is invalid/unsupported for geometry mesh: " + geometryId);
+                            LOG(1, "Warning: Vertex semantic (%s) is invalid/unsupported for geometry mesh: %s\n", semantic.c_str(), geometryId.c_str());
                         }
 
                         DAEPolygonInput* polygonInput = new DAEPolygonInput();
@@ -1669,7 +1669,7 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
                     int type = getVertexUsageType(semantic);
                     if (type == -1)
                     {
-                        warning(std::string("Semantic (") + semantic + ") is invalid/unsupported for geometry mesh: " + geometryId);
+                        LOG(1, "Warning: Semantic (%s) is invalid/unsupported for geometry mesh: %s\n", semantic.c_str(), geometryId.c_str());
                         break;
                     }
                     if (type == TEXCOORD0)
@@ -1709,7 +1709,7 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
                 {
                     delete polygonInputs[j];
                 }
-                warning(std::string("Triangles do not all have the same number of input sources for geometry mesh: ") + geometryId);
+                LOG(1, "Warning: Triangles do not all have the same number of input sources for geometry mesh: %s\n", geometryId.c_str());
                 return NULL;
             }
             else
@@ -1964,16 +1964,6 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
     return mesh;
 }
 
-void DAESceneEncoder::warning(const std::string& message)
-{
-    printf("Warning: %s\n", message.c_str());
-}
-
-void DAESceneEncoder::warning(const char* message)
-{
-    printf("Warning: %s\n", message);
-}
-
 int DAESceneEncoder::getVertexUsageType(const std::string& semantic)
 {
     if (semantic.length() > 0)

+ 0 - 3
gameplay-encoder/src/DAESceneEncoder.h

@@ -156,9 +156,6 @@ private:
      */
     void calcTransform(domNode* domNode, Matrix& dstTransform);
 
-    void warning(const std::string& message);
-    void warning(const char* message);
-
     /**
      * Loads the target data into the animation from the given channel's target.
      * Example: <channel target="Cube/location.X" />

+ 57 - 24
gameplay-encoder/src/EncoderArguments.cpp

@@ -13,12 +13,15 @@ namespace gameplay
 
 static EncoderArguments* __instance;
 
+extern int __logVerbosity = 1;
+
 EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _fontSize(0),
     _parseError(false),
     _fontPreview(false),
     _textOutput(false),
-    _daeOutput(false)
+    _daeOutput(false),
+    _optimizeAnimations(false)
 {
     __instance = this;
 
@@ -170,28 +173,36 @@ bool EncoderArguments::fileExists() const
 
 void EncoderArguments::printUsage() const
 {
-    fprintf(stderr,"Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n");
-    fprintf(stderr,"Supported file extensions:\n");
-    fprintf(stderr,"  .dae\t(COLLADA)\n");
-    fprintf(stderr,"  .fbx\t(FBX)\n");
-    fprintf(stderr,"  .ttf\t(TrueType Font)\n");
-    fprintf(stderr,"\n");
-    fprintf(stderr,"COLLADA and FBX file options:\n");
-    fprintf(stderr,"  -i <id>\tFilter by node ID.\n");
-    fprintf(stderr,"  -t\t\tWrite text/xml.\n");
-    fprintf(stderr,"  -g <node id> <animation id>\n" \
+    LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n");
+    LOG(1, "Supported file extensions:\n");
+    LOG(1, "  .dae\t(COLLADA)\n");
+    LOG(1, "  .fbx\t(FBX)\n");
+    LOG(1, "  .ttf\t(TrueType Font)\n");
+    LOG(1, "\n");
+    LOG(1, "General Options:\n");
+    LOG(1, "  -v <verbosity>\tVerbosity level (0-4).\n");
+    LOG(1, "\n");
+    LOG(1, "COLLADA and FBX file options:\n");
+    LOG(1, "  -i <id>\tFilter by node ID.\n");
+    LOG(1, "  -t\t\tWrite text/xml.\n");
+    LOG(1, "  -g <node id> <animation id>\n" \
         "\t\tGroup all animation channels targeting the nodes into a new animation.\n");
-    fprintf(stderr,"  -h \"<node ids>\" <filename>\n" \
+    LOG(1, "  -oa\n" \
+        "\t\tOptimizes animations by analyzing animation channel data and\n" \
+        "\t\tremoving any channels that contain default/identity values\n" \
+        "\t\tand removing any duplicate contiguous keyframes, which are common\n" \
+        "\t\twhen exporting baked animation data.\n");
+    LOG(1, "  -h \"<node ids>\" <filename>\n" \
         "\t\tGenerates a single heightmap image using meshes from the specified\n" \
         "\t\tnodes. Node id list should be in quotes with a space between each id.\n" \
         "\t\tFilename is the name of the image (PNG) to be saved.\n" \
         "\t\tMultiple -h arguments can be supplied to generate more than one heightmap.\n" \
         "\t\tFor 24-bit packed height data use -hp instead of -h.\n");
-    fprintf(stderr,"\n");
-    fprintf(stderr,"TTF file options:\n");
-    fprintf(stderr,"  -s <size>\tSize of the font.\n");
-    fprintf(stderr,"  -p\t\tOutput font preview.\n");
-    fprintf(stderr, "\n");
+    LOG(1, "\n");
+    LOG(1, "TTF file options:\n");
+    LOG(1, "  -s <size>\tSize of the font.\n");
+    LOG(1, "  -p\t\tOutput font preview.\n");
+    LOG(1, "\n");
     exit(8);
 }
 
@@ -210,6 +221,11 @@ bool EncoderArguments::DAEOutputEnabled() const
     return _daeOutput;
 }
 
+bool EncoderArguments::optimizeAnimationsEnabled() const
+{
+    return _optimizeAnimations;
+}
+
 const char* EncoderArguments::getNodeId() const
 {
     if (_nodeId.length() == 0)
@@ -274,7 +290,7 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             // read one string, make sure not to go out of bounds
             if ((*index + 1) >= options.size())
             {
-                fprintf(stderr, "Error: -dae requires 1 argument.\n");
+                LOG(1, "Error: -dae requires 1 argument.\n");
                 _parseError = true;
                 return;
             }
@@ -289,7 +305,7 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             // read two strings, make sure not to go out of bounds
             if ((*index + 2) >= options.size())
             {
-                fprintf(stderr, "Error: -g requires 2 arguments.\n");
+                LOG(1, "Error: -g requires 2 arguments.\n");
                 _parseError = true;
                 return;
             }
@@ -300,7 +316,6 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
         }
         break;
     case 'i':
-    case 'o':
         // Node ID
         (*index)++;
         if (*index < options.size())
@@ -309,11 +324,19 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
         }
         else
         {
-            fprintf(stderr, "Error: missing arguemnt for -%c.\n", str[1]);
+            LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
             _parseError = true;
             return;
         }
         break;
+    case 'o':
+        // Optimization flag
+        if (str == "-oa")
+        {
+            // Optimize animations
+            _optimizeAnimations = true;
+        }
+        break;
     case 'h':
         {
             bool isHighPrecision = str.compare("-hp") == 0;
@@ -345,7 +368,7 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
                     heightmap.filename = options[*index];
                     if (heightmap.filename.empty())
                     {
-                        fprintf(stderr, "Error: missing filename argument for -h|-heightmap.\n");
+                        LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
                         _parseError = true;
                         return;
                     }
@@ -362,7 +385,7 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
                 }
                 else
                 {
-                    fprintf(stderr, "Error: missing argument for -h|-heightmap.\n");
+                    LOG(1, "Error: missing argument for -h|-heightmap.\n");
                     _parseError = true;
                     return;
                 }
@@ -394,7 +417,7 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
         }
         else
         {
-            fprintf(stderr, "Error: missing arguemnt for -%c.\n", str[1]);
+            LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
             _parseError = true;
             return;
         }
@@ -402,6 +425,16 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
     case 't':
         _textOutput = true;
         break;
+    case 'v':
+        (*index)++;
+        if (*index < options.size())
+        {
+            __logVerbosity = atoi(options[*index].c_str());
+            if (__logVerbosity < 0)
+                __logVerbosity = 0;
+            else if (__logVerbosity > 4)
+                __logVerbosity = 4;
+        }
     default:
         break;
     }

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

@@ -102,6 +102,7 @@ public:
     bool fontPreviewEnabled() const;
     bool textOutputEnabled() const;
     bool DAEOutputEnabled() const;
+    bool optimizeAnimationsEnabled() const;
 
     const char* getNodeId() const;
     unsigned int getFontSize() const;
@@ -146,6 +147,7 @@ private:
     bool _fontPreview;
     bool _textOutput;
     bool _daeOutput;
+    bool _optimizeAnimations;
 
     std::vector<std::string> _groupAnimationNodeId;
     std::vector<std::string> _groupAnimationAnimationId;

+ 260 - 103
gameplay-encoder/src/FBXSceneEncoder.cpp

@@ -121,14 +121,17 @@ static void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix);
 static void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate);
 
 /**
- * Appends a key frame of the given node's transform at the given time.
+ * 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 time The key time to add and the time to get the transform from.
- * @param keyTimes The list of key times to append to.
- * @param keyValues The list of key values to append to.
+ * @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, float time, std::vector<float>* keyTimes, std::vector<float>* keyValues);
+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.
@@ -199,8 +202,8 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
     
     if (!importer->Initialize(filepath.c_str(), -1, sdkManager->GetIOSettings()))
     {
-        printf("Call to FbxImporter::Initialize() failed.\n");
-        printf("Error returned: %s\n\n", importer->GetLastErrorString());
+        LOG(1, "Call to FbxImporter::Initialize() failed.\n");
+        LOG(1, "Error returned: %s\n\n", importer->GetLastErrorString());
         exit(-1);
     }
     
@@ -241,19 +244,19 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
         {
             std::string path = outputFilePath.substr(0, pos);
             path.append(".xml");
-            fprintf(stderr, "Saving debug file: %s\n", path.c_str());
+            LOG(1, "Saving debug file: %s\n", path.c_str());
             if (!_gamePlayFile.saveText(path))
             {
-                fprintf(stderr,"Error writing text file: %s\n", path.c_str());
+                LOG(1, "Error writing text file: %s\n", path.c_str());
             }
         }
     }
     else
     {
-        fprintf(stderr, "Saving binary file: %s\n", outputFilePath.c_str());
+        LOG(1, "Saving binary file: %s\n", outputFilePath.c_str());
         if (!_gamePlayFile.saveBinary(outputFilePath))
         {
-            fprintf(stderr,"Error writing binary file: %s\n", outputFilePath.c_str());
+            LOG(1, "Error writing binary file: %s\n", outputFilePath.c_str());
         }
     }
 }
@@ -377,78 +380,142 @@ void FBXSceneEncoder::loadAnimationChannels(FbxAnimLayer* animLayer, FbxNode* fb
         findMinMaxTime(animCurve, &startTime, &stopTime, &frameRate);
     }
 
-    bool translate = tx | ty | tz;
-    bool scale = sx | sy | sz;
-    bool rotate = rx | ry | rz;
+    if (!(sx || sy || sz || rx || ry || rz || tx || ty || tz))
+        return; // no animation channels
 
-    if (translate || rotate || scale)
-    {
-        assert(startTime != FLT_MAX);
-        assert(stopTime >= 0.0f);
-        AnimationChannel* channel = new AnimationChannel();
-        channel->setTargetId(name);
-        channel->setTargetAttribute(Transform::ANIMATE_SCALE_ROTATE_TRANSLATE);
-        
-        float increment = 1000.0f / frameRate;
-        std::vector<float> keyTimes;
-        std::vector<float> keyValues;
-        for (float time = startTime; time < stopTime; time += increment)
-        {
-            appendKeyFrame(fbxNode, time, &keyTimes, &keyValues);
-        }
-        // Add the last key frame at exactly stopTime
-        appendKeyFrame(fbxNode, stopTime, &keyTimes, &keyValues);
+    assert(startTime != FLT_MAX);
+    assert(stopTime >= 0.0f);
 
-        channel->setKeyTimes(keyTimes);
-        /*
-        std::vector<float> newKeyValues;
-        for (size_t i = 0, size = keyValues.size(); i < size; i += 10)
+    // Determine which animation channels to create
+    std::vector<unsigned int> channelAttribs;
+    if (sx && sy && sz)
+    {
+        if (rx || ry || rz)
         {
-            if (translate)
+            if (tx && ty && tz)
             {
-                newKeyValues.push_back(keyValues[i+0]);
-                newKeyValues.push_back(keyValues[i+1]);
-                newKeyValues.push_back(keyValues[i+2]);
+                channelAttribs.push_back(Transform::ANIMATE_SCALE_ROTATE_TRANSLATE);
             }
-            if (rotate)
-            {
-                newKeyValues.push_back(keyValues[i+3]);
-                newKeyValues.push_back(keyValues[i+4]);
-                newKeyValues.push_back(keyValues[i+5]);
-                newKeyValues.push_back(keyValues[i+6]);
-            }
-            if (scale)
+            else
             {
-                newKeyValues.push_back(keyValues[i+7]);
-                newKeyValues.push_back(keyValues[i+8]);
-                newKeyValues.push_back(keyValues[i+9]);
+                channelAttribs.push_back(Transform::ANIMATE_SCALE_ROTATE);
+                if (tx)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_X);
+                if (ty)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Y);
+                if (tz)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Z);
             }
         }
-        channel->setKeyValues(newKeyValues);
-        */
-        channel->setKeyValues(keyValues);
-        channel->setInterpolation(AnimationChannel::LINEAR);
-        animation->add(channel);
-        /*
-        if (!translate)
+        else
         {
-            addTranslateChannel(animation, fbxNode, startTime, stopTime);
+            if (tx && ty && tz)
+            {
+                channelAttribs.push_back(Transform::ANIMATE_SCALE_TRANSLATE);
+            }
+            else
+            {
+                channelAttribs.push_back(Transform::ANIMATE_SCALE);
+                if (tx)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_X);
+                if (ty)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Y);
+                if (tz)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Z);
+            }
         }
-        if (!rotate)
+    }
+    else
+    {
+        if (rx || ry || rz)
         {
-            printf("rotate?\n"); // TODO
+            if (tx && ty && tz)
+            {
+                channelAttribs.push_back(Transform::ANIMATE_ROTATE_TRANSLATE);
+            }
+            else
+            {
+                channelAttribs.push_back(Transform::ANIMATE_ROTATE);
+                if (tx)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_X);
+                if (ty)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Y);
+                if (tz)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Z);
+            }
         }
-        if (!scale)
+        else
         {
-            addScaleChannel(animation, fbxNode, startTime, stopTime);
+            if (tx && ty && tz)
+            {
+                channelAttribs.push_back(Transform::ANIMATE_TRANSLATE);
+            }
+            else
+            {
+                if (tx)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_X);
+                if (ty)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Y);
+                if (tz)
+                    channelAttribs.push_back(Transform::ANIMATE_TRANSLATE_Z);
+            }
         }
-        */
-        if (_groupAnimation != animation)
+
+        if (sx)
+            channelAttribs.push_back(Transform::ANIMATE_SCALE_X);
+        if (sy)
+            channelAttribs.push_back(Transform::ANIMATE_SCALE_Y);
+        if (sz)
+            channelAttribs.push_back(Transform::ANIMATE_SCALE_Z);
+    }
+    unsigned int channelCount = channelAttribs.size();
+    assert(channelCount > 0);
+
+    // Allocate channel list
+    for (unsigned int i = 0; i < channelCount; ++i)
+    {
+        AnimationChannel* channel = new AnimationChannel();
+        channel->setTargetId(name);
+        channel->setInterpolation(AnimationChannel::LINEAR);
+        channel->setTargetAttribute(channelAttribs[i]);
+        animation->add(channel);
+    }
+
+    // Evaulate animation curve in increments of frameRate and populate channel data.
+    FbxAMatrix fbxMatrix;
+    Matrix matrix;
+    float increment = 1000.0f / frameRate;
+    for (float time = startTime; time <= stopTime; time += increment)
+    {
+        // Clamp time to stopTime
+        time = std::min(time, stopTime);
+
+        // Evalulate the animation at this time
+        FbxTime kTime;
+        kTime.SetMilliSeconds((FbxLongLong)time);
+        fbxMatrix = fbxNode->EvaluateLocalTransform(kTime);
+        copyMatrix(fbxMatrix, matrix);
+
+        // Decompose the evalulated transformation matrix into separate
+        // scale, rotation and translation.
+        Vector3 scale;
+        Quaternion rotation;
+        Vector3 translation;
+        matrix.decompose(&scale, &rotation, &translation);
+        rotation.normalize();
+
+        // Append keyframe data to all channels
+        for (unsigned int i = 0; i < channelCount; ++i)
         {
-            // TODO explains
-            _gamePlayFile.addAnimation(animation);
+            appendKeyFrame(fbxNode, animation->getAnimationChannel(i), time, scale, rotation, translation);
         }
     }
+
+    if (_groupAnimation != animation)
+    {
+        // TODO explain
+        _gamePlayFile.addAnimation(animation);
+    }
 }
 
 void FBXSceneEncoder::loadAnimationLayer(FbxAnimLayer* fbxAnimLayer, FbxNode* fbxNode, const EncoderArguments& arguments)
@@ -576,7 +643,7 @@ void FBXSceneEncoder::saveMesh(FbxUInt64 meshId, Mesh* mesh)
 
 void FBXSceneEncoder::print(const char* str)
 {
-    fprintf(stderr,"%s\n", str);
+    LOG(1, "%s\n", str);
 }
 
 void FBXSceneEncoder::transformNode(FbxNode* fbxNode, Node* node)
@@ -659,7 +726,7 @@ void FBXSceneEncoder::loadCamera(FbxNode* fbxNode, Node* node)
     }
     else
     {
-        warning("Unknown camera type in node");
+        LOG(2, "Warning: Unknown camera type in node.\n");
         return;
     }
     _gamePlayFile.addCamera(camera);
@@ -743,7 +810,7 @@ void FBXSceneEncoder::loadLight(FbxNode* fbxNode, Node* node)
     }
     default:
     {
-        warning("Unknown light type in node.");
+        LOG(2, "Warning: Unknown light type in node.\n");
         return;
     }
     }
@@ -995,16 +1062,6 @@ void FBXSceneEncoder::triangulateRecursive(FbxNode* fbxNode)
     }
 }
 
-void FBXSceneEncoder::warning(const std::string& message)
-{
-    printf("Warning: %s\n", message.c_str());
-}
-
-void FBXSceneEncoder::warning(const char* message)
-{
-    printf("Warning: %s\n", message);
-}
-
 ////////////////////////////////////
 // Functions
 ////////////////////////////////////
@@ -1387,32 +1444,132 @@ void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime,
     *frameRate = std::max(*frameRate, (float)stop.GetFrameRate(FbxTime::eDefaultMode));
 }
 
-void appendKeyFrame(FbxNode* fbxNode, float time, std::vector<float>* keyTimes, std::vector<float>* keyValues)
+void appendKeyFrame(FbxNode* fbxNode, AnimationChannel* channel, float time, const Vector3& scale, const Quaternion& rotation, const Vector3& translation)
 {
-    FbxAMatrix fbxMatrix;
-    Matrix matrix;
-    FbxTime kTime;
-    kTime.SetMilliSeconds((FbxLongLong)time);
-    fbxMatrix = fbxNode->EvaluateLocalTransform(kTime);
-    copyMatrix(fbxMatrix, matrix);
+    // Write key time
+    channel->getKeyTimes().push_back(time);
 
-    Vector3 scale;
-    Quaternion rotation;
-    Vector3 translation;
-    matrix.decompose(&scale, &rotation, &translation);
-    rotation.normalize();
-
-    keyTimes->push_back(time);
-    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);
+    // 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)

+ 0 - 10
gameplay-encoder/src/FBXSceneEncoder.h

@@ -192,16 +192,6 @@ private:
      */
     static void triangulateRecursive(FbxNode* fbxNode);
 
-    /**
-     * Prints a warning message.
-     */
-    static void warning(const std::string& message);
-
-    /**
-     * Prints a warning message.
-     */
-    static void warning(const char* message);
-
 private:
 
     /**

+ 94 - 32
gameplay-encoder/src/GPBFile.cpp

@@ -17,6 +17,11 @@ static GPBFile* __instance = NULL;
  */
 static bool isAlmostOne(float value);
 
+/**
+ * Returns true if the given value is close to zero.
+ */
+static bool isAlmostZero(float value);
+
 /**
  * Gets the common node ancestor for the given list of nodes.
  * This function assumes that the nodes share a common ancestor.
@@ -305,13 +310,17 @@ void GPBFile::adjust()
         }
     }
 
+    LOG(1, "Computing bounding volumes.\n");
     for (std::list<Node*>::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i)
     {
         computeBounds(*i);
     }
 
-    // try to convert joint transform animations into rotation animations
-    //optimizeTransformAnimations();
+    if (EncoderArguments::getInstance()->optimizeAnimationsEnabled())
+    {
+        LOG(1, "Optimizing animations.\n");
+        optimizeAnimations();
+    }
 
     // TODO:
     // remove ambient _lights
@@ -385,27 +394,31 @@ void GPBFile::computeBounds(Node* node)
     }
 }
 
-void GPBFile::optimizeTransformAnimations()
+void GPBFile::optimizeAnimations()
 {
     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();
+
+        LOG(2, "Optimizing %d channel(s) in animation '%s'.\n", channelCount, animation->getId().c_str());
+
         // 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
+
+            // Optimize node animation channels
             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)
+                if (channel->getTargetAttribute() == Transform::ANIMATE_SCALE_ROTATE_TRANSLATE)
                 {
-                    decomposeTransformAnimationChannel(animation, channel);
+                    decomposeTransformAnimationChannel(animation, channel, channelIndex);
 
                     animation->remove(channel);
                     SAFE_DELETE(channel);
@@ -415,8 +428,10 @@ void GPBFile::optimizeTransformAnimations()
     }
 }
 
-void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const AnimationChannel* channel)
+void GPBFile::decomposeTransformAnimationChannel(Animation* animation, AnimationChannel* channel, int channelIndex)
 {
+    LOG(2, "  Optimizing animaton channel %s:%d.\n", animation->getId().c_str(), channelIndex+1);
+
     const std::vector<float>& keyTimes = channel->getKeyTimes();
     const std::vector<float>& keyValues = channel->getKeyValues();
     const size_t keyTimesSize = keyTimes.size();
@@ -425,7 +440,7 @@ void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const Ani
     std::vector<float> scaleKeyValues;
     std::vector<float> rotateKeyValues;
     std::vector<float> translateKeyValues;
-                    
+
     scaleKeyValues.reserve(keyTimesSize * 3);
     rotateKeyValues.reserve(keyTimesSize * 4);
     translateKeyValues.reserve(keyTimesSize * 3);
@@ -450,8 +465,13 @@ void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const Ani
 
     // 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)
+    if (scaleKeyValues.size() == oneCount)
     {
+        LOG(2, "    Discarding scale channel.\n");
+    }
+    else
+    {
+        LOG(3, "    Keeping scale channel.\n");
         AnimationChannel* scaleChannel = new AnimationChannel();
         scaleChannel->setTargetId(channel->getTargetId());
         scaleChannel->setKeyTimes(channel->getKeyTimes());
@@ -464,27 +484,64 @@ void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const Ani
         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);
+    // Don't add the rotation channel if all quaternions are close to identity
+    oneCount = 0;
+    for (unsigned int i = 0, count = rotateKeyValues.size(); i < count; i += 4)
+    {
+        float x = rotateKeyValues[i];
+        float y = rotateKeyValues[i+1];
+        float z = rotateKeyValues[i+2];
+        float w = rotateKeyValues[i+3];
+        if (ISZERO(x) && ISZERO(y) && ISZERO(z) && ISONE(w))
+            ++oneCount;
+        else
+        {
+            LOG(4, "Rotation not identity: %d\n", i);
+            Quaternion q(x, y, z, w);
+            Vector3 axis;
+            float angle = q.toAxisAngle(&axis);
+            angle = 0;
+        }
+    }
+    if ((rotateKeyValues.size()>>2) == oneCount)
+    {
+        LOG(2, "    Discarding rotation channel.\n");
+    }
+    else
+    {
+        LOG(3, "    Keeping rotation channel.\n");
+        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);
+    }
+
+    // Don't add the translation channel if all values are close to zero
+    oneCount = (size_t)std::count_if(translateKeyValues.begin(), translateKeyValues.end(), isAlmostZero);
+    if (translateKeyValues.size() == oneCount)
+    {
+        LOG(2, "    Discarding translation channel.\n");
+    }
+    else
+    {
+        LOG(3, "    Keeping translation channel.\n");
+        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);
+    }
 }
 
 void GPBFile::moveAnimationChannels(Node* node, Animation* dstAnimation)
@@ -517,7 +574,12 @@ void GPBFile::moveAnimationChannels(Node* node, Animation* dstAnimation)
 
 bool isAlmostOne(float value)
 {
-    return std::fabs(value - 1.0f) < EPSILON;
+    return (value - 1.0f) < EPSILON;
+}
+
+bool isAlmostZero(float value)
+{
+    return std::fabs(value) < EPSILON;
 }
 
 Node* getCommonNodeAncestor(const std::vector<Node*>& nodes)

+ 7 - 2
gameplay-encoder/src/GPBFile.h

@@ -121,15 +121,20 @@ private:
      * Computes the bounds of all meshes in the node hierarchy.
      */
     void computeBounds(Node* node);
-    void optimizeTransformAnimations();
+
+    /**
+     * Optimizes animation data by removing unneccessary channels and keyframes.
+     */
+    void optimizeAnimations();
 
     /**
      * 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.
+     * @param channelIndex Index of the channel.
      */
-    void decomposeTransformAnimationChannel(Animation* animation, const AnimationChannel* channel);
+    void decomposeTransformAnimationChannel(Animation* animation, AnimationChannel* channel, int channelIndex);
 
     /**
      * Moves the animation channels that target the given node and its children to be under the given animation.

+ 12 - 12
gameplay-encoder/src/Heightmap.cpp

@@ -38,7 +38,7 @@ bool intersect(const Vector3& rayOrigin, const Vector3& rayDirection, const std:
 
 void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* filename, bool highP)
 {
-    printf("Generating heightmap: %s...\n", filename);
+    LOG(1, "Generating heightmap: %s...\n", filename);
 
     GPBFile* gpbFile = GPBFile::getInstance();
 
@@ -66,18 +66,18 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
             }
             else
             {
-                fprintf(stderr, "WARNING: Node passed to heightmap argument does not have a mesh: %s\n", nodeIds[j].c_str());
+                LOG(1, "WARNING: Node passed to heightmap argument does not have a mesh: %s\n", nodeIds[j].c_str());
             }
         }
         else
         {
-            fprintf(stderr, "WARNING: Failed to locate node for heightmap argument: %s\n", nodeIds[j].c_str());
+            LOG(1, "WARNING: Failed to locate node for heightmap argument: %s\n", nodeIds[j].c_str());
         }
     }
 
     if (meshes.size() == 0)
     {
-        fprintf(stderr, "WARNING: Skipping generation of heightmap '%s'. No nodes found.\n", filename);
+        LOG(1, "WARNING: Skipping generation of heightmap '%s'. No nodes found.\n", filename);
         return;
     }
 
@@ -123,7 +123,7 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
         // Start the processing thread
         if (!createThread(&threads[i], &generateHeightmapChunk, &data))
         {
-            fprintf(stderr, "ERROR: Failed to spawn worker thread for generation of heightmap: %s\n", filename);
+            LOG(1, "ERROR: Failed to spawn worker thread for generation of heightmap: %s\n", filename);
             return;
         }
     }
@@ -144,11 +144,11 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
             maxHeight = threadData[i].maxHeight;
     }
 
-    printf("\r\tDone.\n");
+    LOG(1, "\r\tDone.\n");
 
     if (__failedRayCasts)
     {
-        fprintf(stderr, "Warning: %d triangle intersections failed for heightmap: %s\n", __failedRayCasts, filename);
+        LOG(1, "Warning: %d triangle intersections failed for heightmap: %s\n", __failedRayCasts, filename);
 
         // Go through and clamp any height values that are set to -FLT_MAX to the min recorded height value
         // (otherwise the range of height values will be far too large).
@@ -169,21 +169,21 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
     FILE* fp = fopen(filename, "wb");
     if (fp == NULL)
     {
-        fprintf(stderr, "Error: Failed to open file for writing: %s\n", filename);
+        LOG(1, "Error: Failed to open file for writing: %s\n", filename);
         goto error;
     }
 
     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
     if (png_ptr == NULL)
     {
-        fprintf(stderr, "Error: Write struct creation failed: %s\n", filename);
+        LOG(1, "Error: Write struct creation failed: %s\n", filename);
         goto error;
     }
 
     info_ptr = png_create_info_struct(png_ptr);
     if (info_ptr == NULL)
     {
-        fprintf(stderr, "Error: Info struct creation failed: %s\n", filename);
+        LOG(1, "Error: Info struct creation failed: %s\n", filename);
         goto error;
     }
 
@@ -226,7 +226,7 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
     }
 
     png_write_end(png_ptr, NULL);
-    printf("Saved heightmap: %s\n", filename);
+    LOG(1, "Saved heightmap: %s\n", filename);
 
 error:
     if (heights)
@@ -261,7 +261,7 @@ int generateHeightmapChunk(void* threadData)
 
     for (int z = minZ; z <= maxZ; ++z)
     {
-        printf("\r\t%d%%", (int)(((float)__processedHeightmapScanLines / __totalHeightmapScanlines) * 100.0f));
+        LOG(1, "\r\t%d%%", (int)(((float)__processedHeightmapScanLines / __totalHeightmapScanlines) * 100.0f));
 
         rayOrigin.z = (float)z;
         for (int x = minX; x <= maxX; ++x)

+ 1 - 1
gameplay-encoder/src/Light.cpp

@@ -224,7 +224,7 @@ void Light::setFalloffExponent(float value)
     _falloffExponent = value;
     if ( value != 1.0)
     {
-        printf("Warning: spot light falloff_exponent must be 1.0. \n");
+        LOG(1, "Warning: spot light falloff_exponent must be 1.0. \n");
     }
 }
 

+ 2 - 0
gameplay-encoder/src/Mesh.cpp

@@ -177,6 +177,8 @@ void Mesh::computeBounds()
         return;
     }
 
+    LOG(2, "Computing bounds for mesh: %s\n", getId().c_str());
+
     bounds.min.x = bounds.min.y = bounds.min.z = FLT_MAX;
     bounds.max.x = bounds.max.y = bounds.max.z = -FLT_MAX;
     bounds.center.x = bounds.center.y = bounds.center.z = 0.0f;

+ 14 - 14
gameplay-encoder/src/MeshSkin.cpp

@@ -163,7 +163,7 @@ void MeshSkin::computeBounds()
         return;
     }
 
-    DEBUGPRINT_VARG("\nComputing bounds for skin of mesh: %s\n", _mesh->getId().c_str());
+    LOG(2, "Computing bounds for skin of mesh: %s\n", _mesh->getId().c_str());
 
     Node* joint;
 
@@ -192,7 +192,7 @@ void MeshSkin::computeBounds()
     unsigned int jointCount = _joints.size();
     unsigned int vertexCount = _mesh->getVertexCount();
 
-    DEBUGPRINT_VARG("> %d joints found.\n", jointCount);
+    LOG(3, "  %d joints found.\n", jointCount);
 
     std::vector<AnimationChannel*> channels;
     std::vector<Node*> channelTargets;
@@ -201,8 +201,8 @@ void MeshSkin::computeBounds()
     _jointBounds.resize(jointCount);
 
     // Construct a list of all animation channels that target the joints affecting this mesh skin
-    DEBUGPRINT("> Collecting animations...\n");
-    DEBUGPRINT("> 0%%\r");
+    LOG(3, "  Collecting animations...\n");
+    LOG(3, "  0%%\r");
     for (unsigned int i = 0; i < jointCount; ++i)
     {
         joint = _joints[i];
@@ -272,16 +272,16 @@ void MeshSkin::computeBounds()
         }
         _jointBounds[i] = jointBounds;
 
-        DEBUGPRINT_VARG("> %d%%\r", (int)((float)(i+1) / (float)jointCount * 100.0f));
+        LOG(3, "  %d%%\r", (int)((float)(i+1) / (float)jointCount * 100.0f));
     }
-    DEBUGPRINT("\n");
+    LOG(3, "\n");
 
     unsigned int channelCount = channels.size();
 
     // Create a Curve for each animation channel
     float maxDuration = 0.0f;
-    DEBUGPRINT("> Building animation curves...\n");
-    DEBUGPRINT("> 0%%\r");
+    LOG(3, "  Building animation curves...\n");
+    LOG(3, "  0%%\r");
     for (unsigned int i = 0; i < channelCount; ++i)
     {
         AnimationChannel* channel = channels[i];
@@ -340,9 +340,9 @@ void MeshSkin::computeBounds()
         delete[] keyValues;
         keyValues = NULL;
 
-        DEBUGPRINT_VARG("> %d%%\r", (int)((float)(i+1) / (float)channelCount * 100.0f));
+        LOG(3, "  %d%%\r", (int)((float)(i+1) / (float)channelCount * 100.0f));
     }
-    DEBUGPRINT("\n");
+    LOG(3, "\n");
 
     // Compute a total combined bounding volume for the MeshSkin that contains all possible
     // vertex positions for all animations targeting the skin. This rough approximation allows
@@ -366,8 +366,8 @@ void MeshSkin::computeBounds()
     _mesh->bounds.radius = 0;
     Vector3 skinnedPos;
     Vector3 tempPos;
-    DEBUGPRINT("> Evaluating joints...\n");
-    DEBUGPRINT("> 0%%\r");
+    LOG(3, "  Evaluating joints...\n");
+    LOG(3, "  0%%\r");
     BoundingVolume finalBounds;
     while (time <= maxDuration)
     {
@@ -416,9 +416,9 @@ void MeshSkin::computeBounds()
         else
             time += 33.0f;
 
-        DEBUGPRINT_VARG("> %d%%\r", (int)(time / maxDuration * 100.0f));
+        LOG(3, "  %d%%\r", (int)(time / maxDuration * 100.0f));
     }
-    DEBUGPRINT("\n");
+    LOG(3, "\n");
 
     // Update the bounding sphere for the mesh
     _mesh->bounds = finalBounds;

+ 9 - 9
gameplay-encoder/src/TTFFontEncoder.cpp

@@ -43,7 +43,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
     FT_Error error = FT_Init_FreeType(&library);
     if (error)
     {
-        fprintf(stderr, "FT_Init_FreeType error: %d \n", error);
+        LOG(1, "FT_Init_FreeType error: %d \n", error);
         return -1;
     }
     
@@ -52,7 +52,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
     error = FT_New_Face(library, inFilePath, 0, &face);
     if (error)
     {
-        fprintf(stderr, "FT_New_Face error: %d \n", error);
+        LOG(1, "FT_New_Face error: %d \n", error);
         return -1;
     }
     
@@ -66,7 +66,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
     
     if (error)
     {
-        fprintf(stderr, "FT_Set_Char_Size error: %d \n", error);
+        LOG(1, "FT_Set_Char_Size error: %d \n", error);
         return -1;
     }
 
@@ -74,7 +74,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
     error = FT_Set_Pixel_Sizes(face, FONT_SIZE, 0);
     if (error)
     {
-        fprintf(stderr, "FT_Set_Pixel_Sizes error : %d \n", error);
+        LOG(1, "FT_Set_Pixel_Sizes error : %d \n", error);
         exit(1);
     }
     */
@@ -92,7 +92,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
         error = FT_Load_Char(face, ascii, FT_LOAD_RENDER);
         if (error)
         {
-            fprintf(stderr, "FT_Load_Char error : %d \n", error);
+            LOG(1, "FT_Load_Char error : %d \n", error);
         }
         
         int bitmapRows = slot->bitmap.rows;
@@ -137,7 +137,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
             error = FT_Load_Char(face, ascii, FT_LOAD_RENDER);
             if (error)
             {
-                fprintf(stderr, "FT_Load_Char error : %d \n", error);
+                LOG(1, "FT_Load_Char error : %d \n", error);
             }
 
             // Glyph image.
@@ -204,7 +204,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
         error = FT_Load_Char(face, ascii, FT_LOAD_RENDER);
         if (error)
         {
-            fprintf(stderr, "FT_Load_Char error : %d \n", error);
+            LOG(1, "FT_Load_Char error : %d \n", error);
         }
 
         // Glyph image.
@@ -222,7 +222,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
             penY = row * rowSize;
             if (penY + rowSize > (int)imageHeight)
             {
-                fprintf(stderr, "Image size exceeded!");
+                LOG(1, "Image size exceeded!");
                return -1;
             }
 
@@ -298,7 +298,7 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
     // Close file.
     fclose(gpbFp);
 
-    printf("%s.gpb created successfully. \n", getBaseName(outFilePath).c_str());
+    LOG(1, "%s.gpb created successfully. \n", getBaseName(outFilePath).c_str());
 
     if (fontpreview)
     {

+ 11 - 32
gameplay-encoder/src/Transform.cpp

@@ -16,12 +16,6 @@ const char* Transform::getPropertyString(unsigned int prop)
             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: 
@@ -32,22 +26,14 @@ const char* Transform::getPropertyString(unsigned int prop)
             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";
+        case ANIMATE_SCALE_TRANSLATE:
+            return "ANIMATE_SCALE_TRANSLATE";
+        case ANIMATE_SCALE_ROTATE:
+            return "ANIMATE_SCALE_ROTATE";
         default:
             return "";
     }
@@ -57,31 +43,24 @@ unsigned int Transform::getPropertySize(unsigned int prop)
 {
     switch (prop)
     {
-        case ANIMATE_SCALE_ROTATE_TRANSLATE: 
+        case ANIMATE_SCALE_ROTATE_TRANSLATE:
             return 10;
-        case ANIMATE_ROTATE_TRANSLATE: 
+        case ANIMATE_ROTATE_TRANSLATE:
+        case ANIMATE_SCALE_ROTATE:
             return 7;
+        case ANIMATE_SCALE_TRANSLATE:
+            return 6;
         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_TRANSLATE:
+            return 3;
         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;

+ 10 - 9
gameplay-encoder/src/Transform.h

@@ -17,9 +17,6 @@ public:
         ANIMATE_SCALE_X = 2,
         ANIMATE_SCALE_Y = 3,
         ANIMATE_SCALE_Z = 4,
-        ANIMATE_SCALE_XY = 5,
-        ANIMATE_SCALE_XZ = 6,
-        ANIMATE_SCALE_YZ = 7,
 
         /**
          * Rotation animation property. Data=qx,qy,qz,qw (as quaternion).
@@ -33,22 +30,26 @@ public:
         ANIMATE_TRANSLATE_X = 10,
         ANIMATE_TRANSLATE_Y = 11,
         ANIMATE_TRANSLATE_Z = 12,
-        ANIMATE_TRANSLATE_XY = 13,
-        ANIMATE_TRANSLATE_XZ = 14,
-        ANIMATE_TRANSLATE_YZ = 15,
 
         /**
          * Rotation + Translation animation property(Rigid Body). Data=qx,qy,qz,qw,tx,ty,tz
          */
         ANIMATE_ROTATE_TRANSLATE = 16,
+
         /**
          * Scale, Rotation + Translation animation property. Data=sx,sy,sz,qx,qy,qz,qw,tx,ty,tz
          */
         ANIMATE_SCALE_ROTATE_TRANSLATE = 17,
 
-        ANIMATE_ROTATE_X = 18,
-        ANIMATE_ROTATE_Y = 19,
-        ANIMATE_ROTATE_Z = 20
+        /**
+         * Scale + Translation animation property. Data=sx,sy,sz,tx,ty,tz
+         */
+        ANIMATE_SCALE_TRANSLATE = 18,
+
+        /**
+         * Scale + Rotation animation property. Data=sx,sy,sz,qx,qy,qz,qw
+         */
+        ANIMATE_SCALE_ROTATE = 19
     };
 
     /**

+ 4 - 4
gameplay-encoder/src/main.cpp

@@ -56,12 +56,12 @@ int main(int argc, const char** argv)
     // Check if the file exists.
     if (!arguments.fileExists())
     {
-        fprintf(stderr, "Error: File not found: %s\n", arguments.getFilePathPointer());
+        LOG(1, "Error: File not found: %s\n", arguments.getFilePathPointer());
         return -1;
     }
 
     // File exists
-    fprintf(stderr, "Encoding file: %s\n", arguments.getFilePathPointer());
+    LOG(1, "Encoding file: %s\n", arguments.getFilePathPointer());
 
     switch (arguments.getFileFormat())
     {
@@ -80,7 +80,7 @@ int main(int argc, const char** argv)
             fbxEncoder.write(realpath, arguments);
             break;
 #else
-            fprintf(stderr, "Error: FBX not enabled. Install the FBX SDK and use the preprocessor definition USE_FBX.\n");
+            LOG(1, "Error: FBX not enabled. Install the FBX SDK and use the preprocessor definition USE_FBX.\n");
             return -1;
 #endif
         }
@@ -104,7 +104,7 @@ int main(int argc, const char** argv)
         }
    default:
         {
-            fprintf(stderr, "Error: Unsupported file format: %s\n", arguments.getFilePathPointer());
+            LOG(1, "Error: Unsupported file format: %s\n", arguments.getFilePathPointer());
             return -1;
         }
     }

+ 1 - 1
gameplay/src/AnimationClip.cpp

@@ -169,7 +169,7 @@ float AnimationClip::getBlendWeight() const
 
 bool AnimationClip::isPlaying() const
 {
-    return isClipStateBitSet(CLIP_IS_PLAYING_BIT);
+    return (isClipStateBitSet(CLIP_IS_PLAYING_BIT) && !isClipStateBitSet(CLIP_IS_PAUSED_BIT));
 }
 
 void AnimationClip::play()

+ 6 - 6
gameplay/src/AnimationValue.cpp

@@ -56,18 +56,18 @@ void AnimationValue::setFloat(unsigned int index, float value)
     _value[index] = value;
 }
 
-void AnimationValue::getFloat(float* value, unsigned int offset, unsigned int length) const
+void AnimationValue::getFloats(unsigned int index, float* values, unsigned int count) const
 {
-    GP_ASSERT(_value && value && offset < _componentCount && (offset + length) <= _componentCount);
+    GP_ASSERT(_value && values && index < _componentCount && (index + count) <= _componentCount);
 
-    memcpy(value + offset, _value, length * sizeof(float));
+    memcpy(values, &_value[index], count * sizeof(float));
 }
 
-void AnimationValue::setFloat(float* value, unsigned int offset, unsigned int length)
+void AnimationValue::setFloats(unsigned int index, float* values, unsigned int count)
 {
-    GP_ASSERT(_value && value && offset < _componentCount && (offset + length) <= _componentCount);
+    GP_ASSERT(_value && values && index < _componentCount && (index + count) <= _componentCount);
 
-    memcpy(_value, value + offset, length * sizeof(float));
+    memcpy(&_value[index], values, count * sizeof(float));
 }
 
 }

+ 10 - 10
gameplay/src/AnimationValue.h

@@ -33,22 +33,22 @@ public:
     void setFloat(unsigned int index, float value);
 
     /**
-     * Gets the value of the AnimationValue in a float array.
+     * Copies one or more float values from this AnimationValue into the specified array.
      *
-     * @param value The array to populate with the AnimationValue's values.
-     * @param offset The offset into the value to start populating.
-     * @param length The number of values to copy into the array.
+     * @param index The index to start copying from.
+     * @param values Pointer to float array to copy values into.
+     * @param count Number of values to copy.
      */
-    void getFloat(float* value, unsigned int offset, unsigned int length) const;
+    void getFloats(unsigned int index, float* values, unsigned int count) const;
 
     /**
-     * Sets the value of the AnimationValue.
+     * Copies one or more float values into the AnimationValue.
      *
-     * @param value The array to populate the AnimationValue's values.
-     * @param offset The offset into the value array to start populating from.
-     * @param length The number of values to copy into the AnimationValue.
+     * @param index The index of the first component to set the value for.
+     * @param vaules Array of values to copy into the AnimationValue.
+     * @param count Number of values to in the array to copy in.
      */
-    void setFloat(float* value, unsigned int offset, unsigned int length);
+    void setFloats(unsigned int index, float* values, unsigned int count);
 
 private:
 

+ 0 - 1
gameplay/src/Bundle.cpp

@@ -1307,7 +1307,6 @@ Animation* Bundle::readAnimationChannelData(Animation* animation, const char* id
         return NULL;
     }
 
-    // TODO: Handle other target attributes later.
     if (targetAttribute > 0)
     {
         GP_ASSERT(target);

+ 3 - 12
gameplay/src/MaterialParameter.cpp

@@ -458,22 +458,13 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
                     }
                     break;
                 case VECTOR2:
-                    for (unsigned int i = 0; i < _count; i++)
-                    {
-                        value->setFloat(_value.floatPtrValue, i * 2, 2);
-                    }
+                    value->setFloats(0, _value.floatPtrValue, _count * 2);
                     break;
                 case VECTOR3:
-                    for (unsigned int i = 0; i < _count; i++)
-                    {
-                        value->setFloat(_value.floatPtrValue, i * 3, 3);
-                    }
+                    value->setFloats(0, _value.floatPtrValue, _count * 3);
                     break;
                 case VECTOR4:
-                    for (unsigned int i = 0; i < _count; i++)
-                    {
-                        value->setFloat(_value.floatPtrValue, i * 4, 4);
-                    }
+                    value->setFloats(0, _value.floatPtrValue, _count * 4);
                     break;
                 case NONE:
                 case MATRIX:

+ 31 - 27
gameplay/src/Transform.cpp

@@ -624,7 +624,10 @@ unsigned int Transform::getAnimationPropertyComponentCount(int propertyId) const
             return 3;
         case ANIMATE_ROTATE:
             return 4;
+        case ANIMATE_SCALE_TRANSLATE:
+            return 6;
         case ANIMATE_ROTATE_TRANSLATE:
+        case ANIMATE_SCALE_ROTATE:
             return 7;
         case ANIMATE_SCALE_ROTATE_TRANSLATE:
             return 10;
@@ -643,9 +646,7 @@ void Transform::getAnimationPropertyValue(int propertyId, AnimationValue* value)
             value->setFloat(0, _scale.x);
             break;
         case ANIMATE_SCALE:
-            value->setFloat(0, _scale.x);
-            value->setFloat(1, _scale.y);
-            value->setFloat(2, _scale.z);
+            value->setFloats(0, &_scale.x, 3);
             break;
         case ANIMATE_SCALE_X:
             value->setFloat(0, _scale.x);
@@ -657,15 +658,10 @@ void Transform::getAnimationPropertyValue(int propertyId, AnimationValue* value)
             value->setFloat(0, _scale.z);
             break;
         case ANIMATE_ROTATE:
-            value->setFloat(0, _rotation.x);
-            value->setFloat(1, _rotation.y);
-            value->setFloat(2, _rotation.z);
-            value->setFloat(3, _rotation.w);
+            value->setFloats(0, &_rotation.x, 4);
             break;
         case ANIMATE_TRANSLATE:
-            value->setFloat(0, _translation.x);
-            value->setFloat(1, _translation.y);
-            value->setFloat(2, _translation.z);
+            value->setFloats(0, &_translation.x, 3);
             break;
         case ANIMATE_TRANSLATE_X:
             value->setFloat(0, _translation.x);
@@ -677,25 +673,21 @@ void Transform::getAnimationPropertyValue(int propertyId, AnimationValue* value)
             value->setFloat(0, _translation.z);
             break;
         case ANIMATE_ROTATE_TRANSLATE:
-            value->setFloat(0, _rotation.x);
-            value->setFloat(1, _rotation.y);
-            value->setFloat(2, _rotation.z);
-            value->setFloat(3, _rotation.w);
-            value->setFloat(4, _translation.x);
-            value->setFloat(5, _translation.y);
-            value->setFloat(6, _translation.z);
+            value->setFloats(0, &_rotation.x, 4);
+            value->setFloats(4, &_translation.x, 3);
+            break;
+        case ANIMATE_SCALE_ROTATE:
+            value->setFloats(0, &_scale.x, 3);
+            value->setFloats(3, &_rotation.x, 4);
+            break;
+        case ANIMATE_SCALE_TRANSLATE:
+            value->setFloats(0, &_scale.x, 3);
+            value->setFloats(3, &_translation.x, 3);
             break;
         case ANIMATE_SCALE_ROTATE_TRANSLATE:
-            value->setFloat(0, _scale.x);
-            value->setFloat(1, _scale.y);
-            value->setFloat(2, _scale.z);
-            value->setFloat(3, _rotation.x);
-            value->setFloat(4, _rotation.y);
-            value->setFloat(5, _rotation.z);
-            value->setFloat(6, _rotation.w);
-            value->setFloat(7, _translation.x);
-            value->setFloat(8, _translation.y);
-            value->setFloat(9, _translation.z);
+            value->setFloats(0, &_scale.x, 3);
+            value->setFloats(3, &_rotation.x, 4);
+            value->setFloats(7, &_translation.x, 3);
             break;
         default:
             break;
@@ -766,6 +758,18 @@ void Transform::setAnimationPropertyValue(int propertyId, AnimationValue* value,
             setTranslation(Curve::lerp(blendWeight, _translation.x, value->getFloat(4)), Curve::lerp(blendWeight, _translation.y, value->getFloat(5)), Curve::lerp(blendWeight, _translation.z, value->getFloat(6)));
             break;
         }
+        case ANIMATE_SCALE_ROTATE:
+        {
+            setScale(Curve::lerp(blendWeight, _scale.x, value->getFloat(0)), Curve::lerp(blendWeight, _scale.y, value->getFloat(1)), Curve::lerp(blendWeight, _scale.z, value->getFloat(2)));
+            applyAnimationValueRotation(value, 3, blendWeight);
+            break;
+        }
+        case ANIMATE_SCALE_TRANSLATE:
+        {
+            setScale(Curve::lerp(blendWeight, _scale.x, value->getFloat(0)), Curve::lerp(blendWeight, _scale.y, value->getFloat(1)), Curve::lerp(blendWeight, _scale.z, value->getFloat(2)));
+            setTranslation(Curve::lerp(blendWeight, _translation.x, value->getFloat(3)), Curve::lerp(blendWeight, _translation.y, value->getFloat(4)), Curve::lerp(blendWeight, _translation.z, value->getFloat(5)));
+            break;
+        }
         case ANIMATE_SCALE_ROTATE_TRANSLATE:
         {
             setScale(Curve::lerp(blendWeight, _scale.x, value->getFloat(0)), Curve::lerp(blendWeight, _scale.y, value->getFloat(1)), Curve::lerp(blendWeight, _scale.z, value->getFloat(2)));

+ 10 - 0
gameplay/src/Transform.h

@@ -93,6 +93,16 @@ public:
      */
     static const int ANIMATE_SCALE_ROTATE_TRANSLATE = 17;
 
+    /**
+     * Scale + Translation animation property. Data=sx,sy,sz,tx,ty,tz
+     */
+    static const int ANIMATE_SCALE_TRANSLATE = 18;
+
+    /**
+     * Scale + Rotation animation property. Data=sx,sy,sz,qx,qy,qz,qw
+     */
+    static const int ANIMATE_SCALE_ROTATE = 19;
+
     /**
      * Globally suspends all transform changed events.
      */

+ 52 - 14
gameplay/src/lua/lua_AnimationValue.cpp

@@ -12,7 +12,9 @@ void luaRegister_AnimationValue()
     const luaL_Reg lua_members[] = 
     {
         {"getFloat", lua_AnimationValue_getFloat},
+        {"getFloats", lua_AnimationValue_getFloats},
         {"setFloat", lua_AnimationValue_setFloat},
+        {"setFloats", lua_AnimationValue_setFloats},
         {NULL, NULL}
     };
     const luaL_Reg* lua_statics = NULL;
@@ -59,37 +61,55 @@ int lua_AnimationValue_getFloat(lua_State* state)
             }
             break;
         }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_AnimationValue_getFloats(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
         case 4:
         {
             if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TLIGHTUSERDATA) &&
-                lua_type(state, 3) == LUA_TNUMBER &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                (lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TLIGHTUSERDATA) &&
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                float* param1 = ScriptUtil::getFloatPointer(2);
+                unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 2);
 
                 // Get parameter 2 off the stack.
-                unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 3);
+                float* param2 = ScriptUtil::getFloatPointer(3);
 
                 // Get parameter 3 off the stack.
                 unsigned int param3 = (unsigned int)luaL_checkunsigned(state, 4);
 
                 AnimationValue* instance = getInstance(state);
-                instance->getFloat(param1, param2, param3);
+                instance->getFloats(param1, param2, param3);
                 
                 return 0;
             }
             else
             {
-                lua_pushstring(state, "lua_AnimationValue_getFloat - Failed to match the given parameters to a valid function signature.");
+                lua_pushstring(state, "lua_AnimationValue_getFloats - Failed to match the given parameters to a valid function signature.");
                 lua_error(state);
             }
             break;
         }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 2 or 4).");
+            lua_pushstring(state, "Invalid number of parameters (expected 4).");
             lua_error(state);
             break;
         }
@@ -129,37 +149,55 @@ int lua_AnimationValue_setFloat(lua_State* state)
             }
             break;
         }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 3).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_AnimationValue_setFloats(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
         case 4:
         {
             if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TLIGHTUSERDATA) &&
-                lua_type(state, 3) == LUA_TNUMBER &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                (lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TLIGHTUSERDATA) &&
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                float* param1 = ScriptUtil::getFloatPointer(2);
+                unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 2);
 
                 // Get parameter 2 off the stack.
-                unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 3);
+                float* param2 = ScriptUtil::getFloatPointer(3);
 
                 // Get parameter 3 off the stack.
                 unsigned int param3 = (unsigned int)luaL_checkunsigned(state, 4);
 
                 AnimationValue* instance = getInstance(state);
-                instance->setFloat(param1, param2, param3);
+                instance->setFloats(param1, param2, param3);
                 
                 return 0;
             }
             else
             {
-                lua_pushstring(state, "lua_AnimationValue_setFloat - Failed to match the given parameters to a valid function signature.");
+                lua_pushstring(state, "lua_AnimationValue_setFloats - Failed to match the given parameters to a valid function signature.");
                 lua_error(state);
             }
             break;
         }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 3 or 4).");
+            lua_pushstring(state, "Invalid number of parameters (expected 4).");
             lua_error(state);
             break;
         }

+ 2 - 0
gameplay/src/lua/lua_AnimationValue.h

@@ -6,7 +6,9 @@ namespace gameplay
 
 // Lua bindings for AnimationValue.
 int lua_AnimationValue_getFloat(lua_State* state);
+int lua_AnimationValue_getFloats(lua_State* state);
 int lua_AnimationValue_setFloat(lua_State* state);
+int lua_AnimationValue_setFloats(lua_State* state);
 
 void luaRegister_AnimationValue();
 

+ 36 - 0
gameplay/src/lua/lua_Joint.cpp

@@ -148,7 +148,9 @@ void luaRegister_Joint()
         {"ANIMATE_ROTATE", lua_Joint_static_ANIMATE_ROTATE},
         {"ANIMATE_ROTATE_TRANSLATE", lua_Joint_static_ANIMATE_ROTATE_TRANSLATE},
         {"ANIMATE_SCALE", lua_Joint_static_ANIMATE_SCALE},
+        {"ANIMATE_SCALE_ROTATE", lua_Joint_static_ANIMATE_SCALE_ROTATE},
         {"ANIMATE_SCALE_ROTATE_TRANSLATE", lua_Joint_static_ANIMATE_SCALE_ROTATE_TRANSLATE},
+        {"ANIMATE_SCALE_TRANSLATE", lua_Joint_static_ANIMATE_SCALE_TRANSLATE},
         {"ANIMATE_SCALE_UNIT", lua_Joint_static_ANIMATE_SCALE_UNIT},
         {"ANIMATE_SCALE_X", lua_Joint_static_ANIMATE_SCALE_X},
         {"ANIMATE_SCALE_Y", lua_Joint_static_ANIMATE_SCALE_Y},
@@ -5673,6 +5675,23 @@ int lua_Joint_static_ANIMATE_SCALE(lua_State* state)
     return 1;
 }
 
+int lua_Joint_static_ANIMATE_SCALE_ROTATE(lua_State* state)
+{
+    // Validate the number of parameters.
+    if (lua_gettop(state) > 0)
+    {
+        lua_pushstring(state, "Invalid number of parameters (expected 0).");
+        lua_error(state);
+    }
+
+    int result = Joint::ANIMATE_SCALE_ROTATE;
+
+    // Push the return value onto the stack.
+    lua_pushinteger(state, result);
+
+    return 1;
+}
+
 int lua_Joint_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state)
 {
     // Validate the number of parameters.
@@ -5690,6 +5709,23 @@ int lua_Joint_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state)
     return 1;
 }
 
+int lua_Joint_static_ANIMATE_SCALE_TRANSLATE(lua_State* state)
+{
+    // Validate the number of parameters.
+    if (lua_gettop(state) > 0)
+    {
+        lua_pushstring(state, "Invalid number of parameters (expected 0).");
+        lua_error(state);
+    }
+
+    int result = Joint::ANIMATE_SCALE_TRANSLATE;
+
+    // Push the return value onto the stack.
+    lua_pushinteger(state, result);
+
+    return 1;
+}
+
 int lua_Joint_static_ANIMATE_SCALE_UNIT(lua_State* state)
 {
     // Validate the number of parameters.

+ 2 - 0
gameplay/src/lua/lua_Joint.h

@@ -113,7 +113,9 @@ int lua_Joint_setTranslationZ(lua_State* state);
 int lua_Joint_static_ANIMATE_ROTATE(lua_State* state);
 int lua_Joint_static_ANIMATE_ROTATE_TRANSLATE(lua_State* state);
 int lua_Joint_static_ANIMATE_SCALE(lua_State* state);
+int lua_Joint_static_ANIMATE_SCALE_ROTATE(lua_State* state);
 int lua_Joint_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state);
+int lua_Joint_static_ANIMATE_SCALE_TRANSLATE(lua_State* state);
 int lua_Joint_static_ANIMATE_SCALE_UNIT(lua_State* state);
 int lua_Joint_static_ANIMATE_SCALE_X(lua_State* state);
 int lua_Joint_static_ANIMATE_SCALE_Y(lua_State* state);

+ 36 - 0
gameplay/src/lua/lua_Node.cpp

@@ -146,7 +146,9 @@ void luaRegister_Node()
         {"ANIMATE_ROTATE", lua_Node_static_ANIMATE_ROTATE},
         {"ANIMATE_ROTATE_TRANSLATE", lua_Node_static_ANIMATE_ROTATE_TRANSLATE},
         {"ANIMATE_SCALE", lua_Node_static_ANIMATE_SCALE},
+        {"ANIMATE_SCALE_ROTATE", lua_Node_static_ANIMATE_SCALE_ROTATE},
         {"ANIMATE_SCALE_ROTATE_TRANSLATE", lua_Node_static_ANIMATE_SCALE_ROTATE_TRANSLATE},
+        {"ANIMATE_SCALE_TRANSLATE", lua_Node_static_ANIMATE_SCALE_TRANSLATE},
         {"ANIMATE_SCALE_UNIT", lua_Node_static_ANIMATE_SCALE_UNIT},
         {"ANIMATE_SCALE_X", lua_Node_static_ANIMATE_SCALE_X},
         {"ANIMATE_SCALE_Y", lua_Node_static_ANIMATE_SCALE_Y},
@@ -5626,6 +5628,23 @@ int lua_Node_static_ANIMATE_SCALE(lua_State* state)
     return 1;
 }
 
+int lua_Node_static_ANIMATE_SCALE_ROTATE(lua_State* state)
+{
+    // Validate the number of parameters.
+    if (lua_gettop(state) > 0)
+    {
+        lua_pushstring(state, "Invalid number of parameters (expected 0).");
+        lua_error(state);
+    }
+
+    int result = Node::ANIMATE_SCALE_ROTATE;
+
+    // Push the return value onto the stack.
+    lua_pushinteger(state, result);
+
+    return 1;
+}
+
 int lua_Node_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state)
 {
     // Validate the number of parameters.
@@ -5643,6 +5662,23 @@ int lua_Node_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state)
     return 1;
 }
 
+int lua_Node_static_ANIMATE_SCALE_TRANSLATE(lua_State* state)
+{
+    // Validate the number of parameters.
+    if (lua_gettop(state) > 0)
+    {
+        lua_pushstring(state, "Invalid number of parameters (expected 0).");
+        lua_error(state);
+    }
+
+    int result = Node::ANIMATE_SCALE_TRANSLATE;
+
+    // Push the return value onto the stack.
+    lua_pushinteger(state, result);
+
+    return 1;
+}
+
 int lua_Node_static_ANIMATE_SCALE_UNIT(lua_State* state)
 {
     // Validate the number of parameters.

+ 2 - 0
gameplay/src/lua/lua_Node.h

@@ -112,7 +112,9 @@ int lua_Node_setTranslationZ(lua_State* state);
 int lua_Node_static_ANIMATE_ROTATE(lua_State* state);
 int lua_Node_static_ANIMATE_ROTATE_TRANSLATE(lua_State* state);
 int lua_Node_static_ANIMATE_SCALE(lua_State* state);
+int lua_Node_static_ANIMATE_SCALE_ROTATE(lua_State* state);
 int lua_Node_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state);
+int lua_Node_static_ANIMATE_SCALE_TRANSLATE(lua_State* state);
 int lua_Node_static_ANIMATE_SCALE_UNIT(lua_State* state);
 int lua_Node_static_ANIMATE_SCALE_X(lua_State* state);
 int lua_Node_static_ANIMATE_SCALE_Y(lua_State* state);

+ 36 - 0
gameplay/src/lua/lua_Transform.cpp

@@ -81,7 +81,9 @@ void luaRegister_Transform()
         {"ANIMATE_ROTATE", lua_Transform_static_ANIMATE_ROTATE},
         {"ANIMATE_ROTATE_TRANSLATE", lua_Transform_static_ANIMATE_ROTATE_TRANSLATE},
         {"ANIMATE_SCALE", lua_Transform_static_ANIMATE_SCALE},
+        {"ANIMATE_SCALE_ROTATE", lua_Transform_static_ANIMATE_SCALE_ROTATE},
         {"ANIMATE_SCALE_ROTATE_TRANSLATE", lua_Transform_static_ANIMATE_SCALE_ROTATE_TRANSLATE},
+        {"ANIMATE_SCALE_TRANSLATE", lua_Transform_static_ANIMATE_SCALE_TRANSLATE},
         {"ANIMATE_SCALE_UNIT", lua_Transform_static_ANIMATE_SCALE_UNIT},
         {"ANIMATE_SCALE_X", lua_Transform_static_ANIMATE_SCALE_X},
         {"ANIMATE_SCALE_Y", lua_Transform_static_ANIMATE_SCALE_Y},
@@ -3058,6 +3060,23 @@ int lua_Transform_static_ANIMATE_SCALE(lua_State* state)
     return 1;
 }
 
+int lua_Transform_static_ANIMATE_SCALE_ROTATE(lua_State* state)
+{
+    // Validate the number of parameters.
+    if (lua_gettop(state) > 0)
+    {
+        lua_pushstring(state, "Invalid number of parameters (expected 0).");
+        lua_error(state);
+    }
+
+    int result = Transform::ANIMATE_SCALE_ROTATE;
+
+    // Push the return value onto the stack.
+    lua_pushinteger(state, result);
+
+    return 1;
+}
+
 int lua_Transform_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state)
 {
     // Validate the number of parameters.
@@ -3075,6 +3094,23 @@ int lua_Transform_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state)
     return 1;
 }
 
+int lua_Transform_static_ANIMATE_SCALE_TRANSLATE(lua_State* state)
+{
+    // Validate the number of parameters.
+    if (lua_gettop(state) > 0)
+    {
+        lua_pushstring(state, "Invalid number of parameters (expected 0).");
+        lua_error(state);
+    }
+
+    int result = Transform::ANIMATE_SCALE_TRANSLATE;
+
+    // Push the return value onto the stack.
+    lua_pushinteger(state, result);
+
+    return 1;
+}
+
 int lua_Transform_static_ANIMATE_SCALE_UNIT(lua_State* state)
 {
     // Validate the number of parameters.

+ 2 - 0
gameplay/src/lua/lua_Transform.h

@@ -57,7 +57,9 @@ int lua_Transform_setTranslationZ(lua_State* state);
 int lua_Transform_static_ANIMATE_ROTATE(lua_State* state);
 int lua_Transform_static_ANIMATE_ROTATE_TRANSLATE(lua_State* state);
 int lua_Transform_static_ANIMATE_SCALE(lua_State* state);
+int lua_Transform_static_ANIMATE_SCALE_ROTATE(lua_State* state);
 int lua_Transform_static_ANIMATE_SCALE_ROTATE_TRANSLATE(lua_State* state);
+int lua_Transform_static_ANIMATE_SCALE_TRANSLATE(lua_State* state);
 int lua_Transform_static_ANIMATE_SCALE_UNIT(lua_State* state);
 int lua_Transform_static_ANIMATE_SCALE_X(lua_State* state);
 int lua_Transform_static_ANIMATE_SCALE_Y(lua_State* state);