Преглед изворни кода

Animation:
- Added support for ANIMATE_SCALE_TRANSLATE and ANIMATE_SCALE_ROTATE animation properties on Tranform class.
- Updated AnimationValue interface to support easier bulk set operations.
- Updated AnimationClip::isPlaying() to no longer return true when a clip is paused.

GamePlay encoder:
- Improved animation encoding for FBX by only encoding animation channels that contain data, rather than always storing full Transform/SRT animation data.
- Added option (-oa) for optimizing animations further, for both FBX and DAE files. This option analyzes animation data (which is often baked into full transforms) and attempts to remove any unused channels and any duplicate adjacent keyframe data. This is optional since it is possible for some animtion quality degredation to occur due to matrix decomposition and floating point inaccuracies of the optimization procedure.
- Added more flexible LOG macro to replace existing print statements, which takes log verbosity into consideration.
- Added verbosity option (-v <0-4>) to control how verbose encoder output is.

Steve Grenier пре 13 година
родитељ
комит
c8eda95f47
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);