Ver Fonte

Added auto group animation feature to gameplay-encoder for FBX files.
If the user didn't specify the -g argument and it detects that animations can be grouped for the mesh skins then it will ask the user if they want to auto group the animations.

Darryl Gough há 13 anos atrás
pai
commit
e5e97e6d8e

+ 5 - 0
gameplay-encoder/src/Animations.cpp

@@ -62,4 +62,9 @@ Animation* Animations::getAnimation(unsigned int index) const
     return _animations[index];
 }
 
+void Animations::removeAnimation(unsigned int index)
+{
+    _animations.erase(_animations.begin() + index);
+}
+
 }

+ 1 - 0
gameplay-encoder/src/Animations.h

@@ -32,6 +32,7 @@ public:
     void add(Animation* animation);
     unsigned int getAnimationCount() const;
     Animation* getAnimation(unsigned int index) const;
+    void removeAnimation(unsigned int index);
 
 private:
 

+ 0 - 25
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -34,31 +34,6 @@ unsigned int getMaxOffset(domInputLocalOffset_Array& inputArray)
     return maxOffset;
 }
 
-/**
- * Prompts the user if they want to group animations automatically.
- * If the user enters an invalid response, the question is asked again.
- * 
- * @return True if the user wants to group animations, false otherwise.
- */
-bool promptUserGroupAnimations()
-{
-    char buffer[80];
-    for (;;)
-    {
-        printf("Do you want to group animations ? (y/n)\n");
-        std::cin.getline(buffer, 80);
-        
-        if (buffer[0] == 'y' || buffer[0] == 'Y' || buffer[0] == '\0')
-        {
-            return true;
-        }
-        else if (buffer[0] == 'n' || buffer[0] == 'N')
-        {
-            return false;
-        }
-    }
-}
-
 void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLLADA* dom)
 {
     const std::vector<std::string>& groupAnimatioNodeIds = arguments.getGroupAnimationNodeId();

+ 75 - 1
gameplay-encoder/src/FBXSceneEncoder.cpp

@@ -154,13 +154,24 @@ void addScaleChannel(Animation* animation, FbxNode* fbxNode, float startTime, fl
 
 void addTranslateChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime);
 
+/**
+ * Determines if it is possible to automatically group animations for mesh skins.
+ * 
+ * @param fbxScene The FBX scene to search.
+ * 
+ * @return True if there is at least one mesh skin that has animations that can be grouped.
+ */
+bool isGroupAnimationPossible(FbxScene* fbxScene);
+bool isGroupAnimationPossible(FbxNode* fbxNode);
+bool isGroupAnimationPossible(FbxMesh* fbxMesh);
+
 
 ////////////////////////////////////
 // Member Functions
 ////////////////////////////////////
 
 FBXSceneEncoder::FBXSceneEncoder()
-    : _groupAnimation(NULL)
+    : _groupAnimation(NULL), _autoGroupAnimations(false)
 {
 }
 
@@ -187,6 +198,16 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
     print("Loading FBX file.");
     importer->Import(fbxScene);
     importer->Destroy();
+
+    // Determine if animations should be grouped.
+    if (arguments.getGroupAnimationAnimationId().empty() && isGroupAnimationPossible(fbxScene))
+    {
+        if (promptUserGroupAnimations())
+        {
+            _autoGroupAnimations = true;
+        }
+    }
+
     print("Loading Scene.");
     loadScene(fbxScene);
     print("Loading animations.");
@@ -195,6 +216,10 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
 
     print("Optimizing GamePlay Binary.");
     _gamePlayFile.adjust();
+    if (_autoGroupAnimations)
+    {
+        _gamePlayFile.groupMeshSkinAnimations();
+    }
     
     std::string outputFilePath = arguments.getOutputFilePath();
 
@@ -1473,4 +1498,53 @@ void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix)
     }
 }
 
+bool isGroupAnimationPossible(FbxScene* fbxScene)
+{
+    FbxNode* rootNode = fbxScene->GetRootNode();
+    if (rootNode)
+    {
+        if (isGroupAnimationPossible(rootNode))
+            return true;
+    }
+    return false;
+}
+
+bool isGroupAnimationPossible(FbxNode* fbxNode)
+{
+    if (fbxNode)
+    {
+        FbxMesh* fbxMesh = fbxNode->GetMesh();
+        if (isGroupAnimationPossible(fbxMesh))
+            return true;
+        const int childCount = fbxNode->GetChildCount();
+        for (int i = 0; i < childCount; ++i)
+        {
+            if (isGroupAnimationPossible(fbxNode->GetChild(i)))
+                return true;
+        }
+    }
+    return false;
+}
+
+bool isGroupAnimationPossible(FbxMesh* fbxMesh)
+{
+    if (fbxMesh)
+    {
+        const int deformerCount = fbxMesh->GetDeformerCount();
+        for (int i = 0; i < deformerCount; ++i)
+        {
+            FbxDeformer* deformer = fbxMesh->GetDeformer(i);
+            if (deformer->GetDeformerType() == FbxDeformer::eSkin)
+            {
+                FbxSkin* fbxSkin = static_cast<FbxSkin*>(deformer);
+                if (fbxSkin)
+                {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
 #endif

+ 6 - 1
gameplay-encoder/src/FBXSceneEncoder.h

@@ -215,9 +215,14 @@ private:
     std::map<FbxUInt64, Mesh*> _meshes;
 
     /**
-     * The animation that channels should be added to it the user is using the -groupAnimation command line argument. May be NULL.
+     * The animation that channels should be added to if the user is using the -groupAnimation command line argument. May be NULL.
      */
     Animation* _groupAnimation;
+
+    /**
+     * Indicates if the animations for mesh skins should be grouped before writing out the GPB file.
+     */
+    bool _autoGroupAnimations;
 };
 
 #endif

+ 19 - 0
gameplay-encoder/src/FileIO.cpp

@@ -184,4 +184,23 @@ void writeVectorText(const Vector4& v, FILE* file)
     fprintf(file, "%f %f %f %f\n", v.x, v.y, v.z, v.w);
 }
 
+bool promptUserGroupAnimations()
+{
+    char buffer[80];
+    for (;;)
+    {
+        printf("Do you want to group animations? (y/n)\n");
+        std::cin.getline(buffer, 80);
+        
+        if (buffer[0] == 'y' || buffer[0] == 'Y' || buffer[0] == '\0')
+        {
+            return true;
+        }
+        else if (buffer[0] == 'n' || buffer[0] == 'N')
+        {
+            return false;
+        }
+    }
+}
+
 }

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

@@ -136,6 +136,14 @@ void writeVectorBinary(const Vector4& v, FILE* file);
 
 void writeVectorText(const Vector4& v, FILE* file);
 
+/**
+ * Prompts the user if they want to group animations automatically.
+ * If the user enters an invalid response, the question is asked again.
+ * 
+ * @return True if the user wants to group animations, false otherwise.
+ */
+bool promptUserGroupAnimations();
+
 }
 
 #endif

+ 106 - 9
gameplay-encoder/src/GPBFile.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "GPBFile.h"
 #include "Transform.h"
+#include "StringUtil.h"
 
 #define EPSILON 1.2e-7f;
 
@@ -14,6 +15,26 @@ static GPBFile* __instance = NULL;
  */
 static bool isAlmostOne(float value);
 
+/**
+ * Gets the common node ancestor for the given list of nodes.
+ * This function assumes that the nodes share a common ancestor.
+ * 
+ * @param nodes The list of nodes.
+ * 
+ * @return The common node ancestor or NULL if the list of was empty.
+ */
+static Node* getCommonNodeAncestor(const std::vector<Node*>& nodes);
+
+/**
+ * Gets the list of node ancestors for the given node.
+ * 
+ * @param node The node to get the ancestors for.
+ * @param ancestors The output list of ancestors. 
+ *                  The first element is the root node and the last element is the direct parent of the node.
+ */
+static void getNodeAncestors(Node* node, std::list<Node*>& ancestors);
+
+
 GPBFile::GPBFile(void)
     : _file(NULL), _animationsAdded(false)
 {
@@ -304,6 +325,30 @@ void GPBFile::adjust()
     //   This can be merged into one animation. Same for scale animations.
 }
 
+void GPBFile::groupMeshSkinAnimations()
+{
+    for (std::list<Node*>::iterator it = _nodes.begin(); it != _nodes.end(); ++it)
+    {
+        if (Model* model = (*it)->getModel())
+        {
+            if (MeshSkin* skin = model->getSkin())
+            {
+                const std::vector<Node*>& joints = skin->getJoints();
+                Node* commonAncestor = getCommonNodeAncestor(joints);
+                if (commonAncestor)
+                {
+                    // group the animation channels that target this common ancestor and its child nodes
+                    Animation* animation = new Animation();
+                    animation->setId("animations");
+
+                    moveAnimationChannels(commonAncestor, animation);
+                    _animations.add(animation);
+                }
+            }
+        }
+    }
+}
+
 void GPBFile::renameAnimations(std::vector<std::string>& animationIds, const char* newId)
 {
     const unsigned int animationCount = _animations.getAnimationCount();
@@ -328,14 +373,6 @@ void GPBFile::computeBounds(Node* node)
         {
             mesh->computeBounds();
         }
-        if (MeshSkin* skin = model->getSkin())
-        {
-            skin->computeBounds();
-        }
-    }
-    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
-    {
-        computeBounds(child);
     }
 }
 
@@ -442,9 +479,69 @@ void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const Ani
     animation->add(translateChannel);
 }
 
-static bool isAlmostOne(float value)
+void GPBFile::moveAnimationChannels(Node* node, Animation* dstAnimation)
+{
+    // Loop through the animations and channels backwards because they will be removed when found.
+    int animationCount = _animations.getAnimationCount();
+    for (int i = animationCount - 1; i >= 0; --i)
+    {
+        Animation* animation = _animations.getAnimation(i);
+        int channelCount = animation->getAnimationChannelCount();
+        for (int j = channelCount - 1; j >= 0; --j)
+        {
+            AnimationChannel* channel = animation->getAnimationChannel(j);
+            if (equals(channel->getTargetId(), node->getId()))
+            {
+                animation->remove(channel);
+                dstAnimation->add(channel);
+            }
+        }
+        if (animation->getAnimationChannelCount() == 0)
+        {
+            _animations.removeAnimation(i);
+        }
+    }
+    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
+    {
+        moveAnimationChannels(child, dstAnimation);
+    }
+}
+
+bool isAlmostOne(float value)
 {
     return std::fabs(value - 1.0f) < EPSILON;
 }
 
+Node* getCommonNodeAncestor(const std::vector<Node*>& nodes)
+{
+    if (nodes.empty())
+        return NULL;
+    if (nodes.size() == 1)
+        return nodes.front();
+
+    std::list<Node*> ancestors;
+    size_t minAncestorCount = INT_MAX;
+    for (std::vector<Node*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
+    {
+        Node* node = *it;
+        getNodeAncestors(node, ancestors);
+        ancestors.push_back(node);
+        minAncestorCount = std::min(minAncestorCount, ancestors.size());
+    }
+    ancestors.resize(minAncestorCount);
+
+    return ancestors.back();
+}
+
+void getNodeAncestors(Node* node, std::list<Node*>& ancestors)
+{
+    ancestors.clear();
+    Node* parent = node->getParent();
+    while (parent != NULL)
+    {
+        ancestors.push_front(parent);
+        parent = parent->getParent();
+    }
+}
+
 }

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

@@ -102,6 +102,11 @@ public:
      */
     void adjust();
 
+    /**
+     * Groups the animations of all mesh skins to be under one animation per mesh skin.
+     */
+    void groupMeshSkinAnimations();
+
     /**
      * Renames the animations in the list of animation ids to the new animation id.
      * 
@@ -125,6 +130,14 @@ private:
      */
     void decomposeTransformAnimationChannel(Animation* animation, const AnimationChannel* channel);
 
+    /**
+     * Moves the animation channels that target the given node and its children to be under the given animation.
+     * 
+     * @param node The node to recursively search from.
+     * @param animation The animation to move the channels to.
+     */
+    void moveAnimationChannels(Node* node, Animation* animation);
+
 private:
 
     FILE* _file;
@@ -132,6 +145,9 @@ private:
     std::list<Camera*> _cameras;
     std::list<Light*> _lights;
     std::list<Mesh*> _geometry;
+    /**
+     * The flat list of all nodes.
+     */
     std::list<Node*> _nodes;
     Animations _animations;
     bool _animationsAdded;

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

@@ -109,6 +109,11 @@ const std::vector<std::string>& MeshSkin::getJointNames()
     return _jointNames;
 }
 
+const std::vector<Node*>& MeshSkin::getJoints() const
+{
+    return _joints;
+}
+
 void MeshSkin::setJoints(const std::vector<Node*>& list)
 {
     _joints = list;

+ 3 - 1
gameplay-encoder/src/MeshSkin.h

@@ -38,9 +38,11 @@ public:
 
     void setVertexInfluenceCount(unsigned int count);
 
+    const std::vector<std::string>& getJointNames();
+
     void setJointNames(const std::vector<std::string>& list);
 
-    const std::vector<std::string>& getJointNames();
+    const std::vector<Node*>& getJoints() const;
 
     void setJoints(const std::vector<Node*>& list);