Selaa lähdekoodia

Added a feature to gameplay encoder to auto group animations for mesh skins.
If the encoder detects a mesh skin with multiple animated joints and those joint animations are not grouped then ask the user if they want the animations to be grouped.
This check is not performed if the user specified -g or -groupAnimations as a command line argument.

Darryl Gough 13 vuotta sitten
vanhempi
sitoutus
d1cff4886e

+ 68 - 9
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -34,21 +34,79 @@ unsigned int getMaxOffset(domInputLocalOffset_Array& inputArray)
     return maxOffset;
     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)
 void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLLADA* dom)
 {
 {
-    DAEOptimizer optimizer(dom);
     const std::vector<std::string>& groupAnimatioNodeIds = arguments.getGroupAnimationNodeId();
     const std::vector<std::string>& groupAnimatioNodeIds = arguments.getGroupAnimationNodeId();
     const std::vector<std::string>& groupAnimatioIds = arguments.getGroupAnimationAnimationId();
     const std::vector<std::string>& groupAnimatioIds = arguments.getGroupAnimationAnimationId();
     assert(groupAnimatioNodeIds.size() == groupAnimatioIds.size());
     assert(groupAnimatioNodeIds.size() == groupAnimatioIds.size());
-    size_t size = groupAnimatioNodeIds.size();
-    if (size > 0)
+    if (!groupAnimatioNodeIds.empty())
     {
     {
-        begin();
-        for (size_t i = 0; i < size; ++i)
+        size_t size = groupAnimatioNodeIds.size();
+        if (size > 0)
         {
         {
-            optimizer.combineAnimations(groupAnimatioNodeIds[i], groupAnimatioIds[i]);
+            DAEOptimizer optimizer(dom);
+            begin();
+            for (size_t i = 0; i < size; ++i)
+            {
+                optimizer.combineAnimations(groupAnimatioNodeIds[i], groupAnimatioIds[i]);
+            }
+            end("groupAnimation");
+        }
+    }
+    else
+    {
+        // Determine if there are any mesh skins that are animated that have more than 1 animation targeting its joints.
+        // (candidates for grouping)
+        std::vector<std::string> nodeIds;
+        if (findGroupAnimationNodes(dom, nodeIds))
+        {
+            // Ask the user if they want to group animations automatically.
+            if (promptUserGroupAnimations())
+            {
+                printf("Grouping animations...\n");
+
+                DAEOptimizer optimizer(dom);
+                begin();
+                char buffer[20];
+                size_t size = nodeIds.size();
+                for (size_t i = 0; i < size; ++i)
+                {
+                    // In COLLADA, ids must be unique but they don't have to be unique in GPB.
+                    // Save the animation id as "animations___#" and then rename it once the GPB objects are created
+                    // but before the GPB is written to file.
+                    sprintf(buffer, "animations___%d", i);
+                    std::string animationId(buffer);
+                    _tempGroupAnimationIds.push_back(animationId);
+                    optimizer.combineAnimations(nodeIds[i], animationId);
+                }
+                end("groupAnimation");
+            }
         }
         }
-        end("groupAnimation");
     }
     }
     if (arguments.DAEOutputEnabled())
     if (arguments.DAEOutputEnabled())
     {
     {
@@ -306,6 +364,7 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
     end("loadAnimations");
     end("loadAnimations");
 
 
     _gamePlayFile.adjust();
     _gamePlayFile.adjust();
+    _gamePlayFile.renameAnimations(_tempGroupAnimationIds, "animations");
 
 
     // Write the output file
     // Write the output file
     std::string outputFilePath = arguments.getOutputFilePath();
     std::string outputFilePath = arguments.getOutputFilePath();
@@ -626,7 +685,7 @@ bool DAESceneEncoder::loadTarget(const domChannelRef& channelRef, AnimationChann
             }
             }
             else if (type == domMatrix::ID())
             else if (type == domMatrix::ID())
             {
             {
-                // If the animation is targetting a matrix then convert it into
+                // If the animation is targeting a matrix then convert it into
                 // a scale, rotate, translate animation by decomposing the matrix.
                 // a scale, rotate, translate animation by decomposing the matrix.
                 targetProperty = Transform::ANIMATE_SCALE_ROTATE_TRANSLATE;
                 targetProperty = Transform::ANIMATE_SCALE_ROTATE_TRANSLATE;
 
 
@@ -1333,7 +1392,7 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
             std::vector<std::string> list;
             std::vector<std::string> list;
             getJointNames(source, list);
             getJointNames(source, list);
 
 
-            // Go through the joint list and conver them from sid to id because the sid information is
+            // Go through the joint list and convert them from sid to id because the sid information is
             // lost when converting to the gameplay binary format.
             // lost when converting to the gameplay binary format.
             for (std::vector<std::string>::iterator i = list.begin(); i != list.end(); i++)
             for (std::vector<std::string>::iterator i = list.begin(); i != list.end(); i++)
             {
             {

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

@@ -204,6 +204,8 @@ private:
     float* _vertexBlendWeights;
     float* _vertexBlendWeights;
     unsigned int* _vertexBlendIndices;
     unsigned int* _vertexBlendIndices;
 
 
+    std::vector<std::string> _tempGroupAnimationIds;
+
     clock_t _begin;
     clock_t _begin;
 };
 };
 
 

+ 130 - 0
gameplay-encoder/src/DAEUtil.cpp

@@ -1,3 +1,4 @@
+#include <set>
 #include "Base.h"
 #include "Base.h"
 #include "DAEUtil.h"
 #include "DAEUtil.h"
 #include "StringUtil.h"
 #include "StringUtil.h"
@@ -421,4 +422,133 @@ domVisual_scene* getVisualScene(const domCOLLADA::domSceneRef& domScene)
     return NULL;
     return NULL;
 }
 }
 
 
+domNode* getParent(domNodeRef node)
+{
+    daeElement* parent = node->getParent();
+    if (parent && parent->getElementType() == COLLADA_TYPE::NODE)
+    {
+        domNodeRef parentNode = daeSafeCast<domNode>(parent);
+        return parentNode.cast();
+    }
+    return NULL;
+}
+
+domAnimation* getAnimation(domChannelRef channel)
+{
+    daeElement* parent = channel->getParent();
+    if (parent && parent->getElementType() == COLLADA_TYPE::ANIMATION)
+    {
+        domAnimationRef parentNode = daeSafeCast<domAnimation>(parent);
+        return parentNode.cast();
+    }
+    return NULL;
+}
+
+domNode* getCommonNodeAncestor(std::list<domNodeRef>& nodes)
+{
+    if (nodes.empty())
+        return NULL;
+    if (nodes.size() == 1)
+        return nodes.begin()->cast();
+
+    std::list<domNode*> ancestors;
+    size_t minAncestorCount = INT_MAX;
+    for (std::list<domNodeRef>::iterator it = nodes.begin(); it != nodes.end(); ++it)
+    {
+        domNodeRef& node = *it;
+        getNodeAncestors(node, ancestors);
+        ancestors.push_back(node.cast());
+        minAncestorCount = std::min(minAncestorCount, ancestors.size());
+    }
+    ancestors.resize(minAncestorCount);
+
+    return ancestors.back();
+}
+
+void getNodeAncestors(domNodeRef& node, std::list<domNode*>& ancestors)
+{
+    ancestors.clear();
+    domNode* parent = getParent(node);
+    while (parent != NULL)
+    {
+        ancestors.push_front(parent);
+        parent = getParent(parent);
+    }
+}
+
+bool findGroupAnimationNodes(domCOLLADA* dom, std::vector<std::string>& nodesToGroup)
+{
+    bool groupPossible = false;
+    const domLibrary_controllers_Array& controllersArrays = dom->getLibrary_controllers_array();
+    size_t controllersArraysCount = controllersArrays.getCount();
+    for (size_t i = 0; i < controllersArraysCount; ++i)
+    {
+        const domLibrary_controllersRef& libraryController = controllersArrays.get(i);
+        const domController_Array& controllerArray = libraryController->getController_array();
+        size_t controllerCount = controllerArray.getCount();
+        for (size_t j = 0; j < controllerCount; ++j)
+        {
+            const domControllerRef& controllerRef = controllerArray.get(j);
+            const domSkinRef& skinRef = controllerRef->getSkin();
+            if (skinRef.cast() != NULL)
+            {
+                domSkin::domJointsRef joints = skinRef->getJoints();
+                domInputLocal_Array& jointInputs = joints->getInput_array();
+                for (unsigned int i = 0; i < jointInputs.getCount(); ++i)
+                {
+                    domInputLocalRef input = jointInputs.get(i);
+                    std::string inputSemantic = std::string(input->getSemantic());
+                    domURIFragmentType* sourceURI = &input->getSource();
+                    sourceURI->resolveElement();
+                    const domSourceRef source = (domSource*)(daeElement*)sourceURI->getElement();
+                    if (equals(inputSemantic, "JOINT"))
+                    {
+                        std::list<domChannelRef> channels;
+                        std::list<domNodeRef> nodes;
+                        findChannelsTargetingJoints(source, channels, nodes);
+                        // If the channels don't share the same animation then they can be grouped.
+                        if (!sameAnimation(channels))
+                        {
+                            groupPossible = true;
+                            domNode* parentMost = getCommonNodeAncestor(nodes);
+                            nodesToGroup.push_back(parentMost->getId());
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return groupPossible;
+}
+
+bool sameAnimation(std::list<domChannelRef>& channels)
+{
+    std::list<domChannelRef>::iterator it = channels.begin();
+    domAnimation* temp = getAnimation(*it);
+    ++it;
+    for (; it != channels.end(); ++it)
+    {
+        if (getAnimation(*it) != temp)
+            return false;
+    }
+    return true;
+}
+
+void findChannelsTargetingJoints(const domSourceRef& source, std::list<domChannelRef>& channels, std::list<domNodeRef>& nodes)
+{
+    std::vector<std::string> jointNames;
+    getJointNames(source, jointNames);
+    for (std::vector<std::string>::iterator i = jointNames.begin(); i != jointNames.end(); i++)
+    {
+        daeSIDResolver resolver(source->getDocument()->getDomRoot(), i->c_str());
+        daeElement* element = resolver.getElement();
+        if (element && element->getElementType() == COLLADA_TYPE::NODE)
+        {
+            domNodeRef node = daeSafeCast<domNode>(element);
+            nodes.push_back(node);
+            getAnimationChannels(node, channels);
+        }
+    }
+}
+
 }
 }

+ 67 - 1
gameplay-encoder/src/DAEUtil.h

@@ -126,12 +126,78 @@ bool isEmptyAnimation(domAnimationRef& animation);
 /**
 /**
  * Gets the visual scene from the given COLLADA dom scene.
  * Gets the visual scene from the given COLLADA dom scene.
  * 
  * 
- * @param COLLADA dom scene.
+ * @param domScene The dom scene.
  * 
  * 
  * @return The visual scene or NULL if not found.
  * @return The visual scene or NULL if not found.
  */
  */
 domVisual_scene* getVisualScene(const domCOLLADA::domSceneRef& domScene);
 domVisual_scene* getVisualScene(const domCOLLADA::domSceneRef& domScene);
 
 
+/**
+ * Returns the parent node of the given node or NULL if there is no parent.
+ * 
+ * @param node The node to get the parent for.
+ * 
+ * @return The parent node or NULL if the node does not have a parent node.
+ */
+domNode* getParent(domNodeRef node);
+
+/**
+ * Returns the animation for the given channel.
+ * 
+ * @param channel The animation channel to get the animation for.
+ * 
+ * @return The animation of the channel or NULL if the channel does not belong to an animation.
+ */
+domAnimation* getAnimation(domChannelRef channel);
+
+/**
+ * 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.
+ */
+domNode* getCommonNodeAncestor(std::list<domNodeRef>& 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.
+ */
+void getNodeAncestors(domNodeRef& node, std::list<domNode*>& ancestors);
+
+/**
+ * Finds the nodes that can be automatically grouped because there is a mesh skin that has joints 
+ * that are being targetted by animations that are not grouped.
+ * 
+ * @param dom The COLLADA dom.
+ * @param nodesToGroup The list of node IDs that can have their animations automatically grouped under.
+ * 
+ * @return True if there are mesh skins that can have their animations grouped, false otherwise.
+ */
+bool findGroupAnimationNodes(domCOLLADA* dom, std::vector<std::string>& nodesToGroup);
+
+/**
+ * Returns true if the list of animation channels share the same animation.
+ * 
+ * @param channels The list of channels.
+ * 
+ * @return True if the channels share the same animation, false otherwis.
+ */ 
+bool sameAnimation(std::list<domChannelRef>& channels);
+
+/**
+ * Finds the animation channels that target the given joints and the list of nodes that are targetted by those channels.
+ * 
+ * @param source The source element to get the list of joints from.
+ * @param channels The output list of channels.
+ * @param nodes The output list of nodes.
+ */
+void findChannelsTargetingJoints(const domSourceRef& source, std::list<domChannelRef>& channels, std::list<domNodeRef>& nodes);
+
 }
 }
 
 
 #endif
 #endif

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

@@ -180,7 +180,7 @@ void EncoderArguments::printUsage() const
     fprintf(stderr,"  -i <id>\t\tFilter by node ID.\n");
     fprintf(stderr,"  -i <id>\t\tFilter by node ID.\n");
     fprintf(stderr,"  -t\t\t\tWrite text/xml.\n");
     fprintf(stderr,"  -t\t\t\tWrite text/xml.\n");
     fprintf(stderr,"  -g <node id> <animation id>\n" \
     fprintf(stderr,"  -g <node id> <animation id>\n" \
-        "\t\t\tGroup all animation channels targetting the nodes into a new animation.\n");
+        "\t\t\tGroup all animation channels targeting the nodes into a new animation.\n");
     fprintf(stderr,"  -h \"<node ids>\"\n" \
     fprintf(stderr,"  -h \"<node ids>\"\n" \
         "\t\t\tList of nodes to generate heightmaps for.\n" \
         "\t\t\tList of nodes to generate heightmaps for.\n" \
         "\t\t\tNode id list should be in quotes with a space between each id.\n" \
         "\t\t\tNode id list should be in quotes with a space between each id.\n" \

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

@@ -76,7 +76,7 @@ public:
     const std::vector<std::string>& getHeightmapNodeIds() const;
     const std::vector<std::string>& getHeightmapNodeIds() const;
 
 
     /**
     /**
-     * Returns true if an error occured while parsing the command line arguments.
+     * Returns true if an error occurred while parsing the command line arguments.
      */
      */
     bool parseErrorOccured() const;
     bool parseErrorOccured() const;
 
 

+ 16 - 1
gameplay-encoder/src/GPBFile.cpp

@@ -300,10 +300,25 @@ void GPBFile::adjust()
     //
     //
     // merge animations if possible
     // merge animations if possible
     //   Search for animations that have the same target and key times and see if they can be merged.
     //   Search for animations that have the same target and key times and see if they can be merged.
-    //   Blender will output a simple translation animation to 3 separate animations with the same key times but targetting X, Y and Z.
+    //   Blender will output a simple translation animation to 3 separate animations with the same key times but targeting X, Y and Z.
     //   This can be merged into one animation. Same for scale animations.
     //   This can be merged into one animation. Same for scale animations.
 }
 }
 
 
+void GPBFile::renameAnimations(std::vector<std::string>& animationIds, const char* newId)
+{
+    const unsigned int animationCount = _animations.getAnimationCount();
+    for (unsigned int animationIndex = 0; animationIndex < animationCount; ++animationIndex)
+    {
+        Animation* animation = _animations.getAnimation(animationIndex);
+        assert(animation);
+        std::vector<std::string>::const_iterator it = find(animationIds.begin(), animationIds.end(), animation->getId());
+        if (it != animationIds.end())
+        {
+            animation->setId(newId);
+        }
+    }
+}
+
 void GPBFile::computeBounds(Node* node)
 void GPBFile::computeBounds(Node* node)
 {
 {
     assert(node);
     assert(node);

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

@@ -102,6 +102,14 @@ public:
      */
      */
     void adjust();
     void adjust();
 
 
+    /**
+     * Renames the animations in the list of animation ids to the new animation id.
+     * 
+     * @param animationIds The list of animations to rename.
+     * @param newId The new animation id.
+     */
+    void renameAnimations(std::vector<std::string>& animationIds, const char* newId);
+
 private:
 private:
     /**
     /**
      * Computes the bounds of all meshes in the node hierarchy.
      * Computes the bounds of all meshes in the node hierarchy.

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

@@ -340,7 +340,7 @@ void MeshSkin::computeBounds()
     DEBUGPRINT("\n");
     DEBUGPRINT("\n");
 
 
     // Compute a total combined bounding volume for the MeshSkin that contains all possible
     // Compute a total combined bounding volume for the MeshSkin that contains all possible
-    // vertex positions for all animations targetting the skin. This rough approximation allows
+    // vertex positions for all animations targeting the skin. This rough approximation allows
     // us to store a volume that can be used for rough intersection tests (such as for visibility
     // us to store a volume that can be used for rough intersection tests (such as for visibility
     // determination) efficiently at runtime.
     // determination) efficiently at runtime.
 
 

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

@@ -14,7 +14,7 @@ class Matrix;
  *
  *
  * Quaternions are typically used as a replacement for euler angles and rotation matrices as a way to achieve smooth interpolation and avoid gimbal lock.
  * Quaternions are typically used as a replacement for euler angles and rotation matrices as a way to achieve smooth interpolation and avoid gimbal lock.
  *
  *
- * Note that this quaternion class does not automatically keep the quaternion normalized. Therefore, care must be taken to normalize the quaternion when neccessary, by calling the normalize method.
+ * Note that this quaternion class does not automatically keep the quaternion normalized. Therefore, care must be taken to normalize the quaternion when necessary, by calling the normalize method.
  * The package provides three methods for doing quaternion interpolation: lerp, slerp, and squad.
  * The package provides three methods for doing quaternion interpolation: lerp, slerp, and squad.
  *
  *
  * lerp (linear interpolation): the interpolation curve gives a straight line in quaternion space. It is simple and fast to compute. The only problem is that it does not provide constant angular velocity. Note that a constant velocity is not necessarily a requirement for a curve;
  * lerp (linear interpolation): the interpolation curve gives a straight line in quaternion space. It is simple and fast to compute. The only problem is that it does not provide constant angular velocity. Note that a constant velocity is not necessarily a requirement for a curve;

+ 2 - 2
gameplay-encoder/src/Vector2.h

@@ -163,7 +163,7 @@ public:
     /**
     /**
      * Returns the squared distance between this vector and v.
      * Returns the squared distance between this vector and v.
      *
      *
-     * When it is not neccessary to get the exact distance between
+     * When it is not necessary to get the exact distance between
      * two vectors (for example, when simply comparing the
      * two vectors (for example, when simply comparing the
      * distance between different vectors), it is advised to use
      * distance between different vectors), it is advised to use
      * this method instead of distance.
      * this method instead of distance.
@@ -207,7 +207,7 @@ public:
     /**
     /**
      * Returns the squared length of this vector.
      * Returns the squared length of this vector.
      *
      *
-     * When it is not neccessary to get the exact length of a
+     * When it is not necessary to get the exact length of a
      * vector (for example, when simply comparing the lengths of
      * vector (for example, when simply comparing the lengths of
      * different vectors), it is advised to use this method
      * different vectors), it is advised to use this method
      * instead of length.
      * instead of length.

+ 2 - 2
gameplay-encoder/src/Vector3.h

@@ -210,7 +210,7 @@ public:
     /**
     /**
      * Returns the squared distance between this vector and v.
      * Returns the squared distance between this vector and v.
      *
      *
-     * When it is not neccessary to get the exact distance between
+     * When it is not necessary to get the exact distance between
      * two vectors (for example, when simply comparing the
      * two vectors (for example, when simply comparing the
      * distance between different vectors), it is advised to use
      * distance between different vectors), it is advised to use
      * this method instead of distance.
      * this method instead of distance.
@@ -254,7 +254,7 @@ public:
     /**
     /**
      * Returns the squared length of this vector.
      * Returns the squared length of this vector.
      *
      *
-     * When it is not neccessary to get the exact length of a
+     * When it is not necessary to get the exact length of a
      * vector (for example, when simply comparing the lengths of
      * vector (for example, when simply comparing the lengths of
      * different vectors), it is advised to use this method
      * different vectors), it is advised to use this method
      * instead of length.
      * instead of length.

+ 2 - 2
gameplay-encoder/src/Vector4.h

@@ -201,7 +201,7 @@ public:
     /**
     /**
      * Returns the squared distance between this vector and v.
      * Returns the squared distance between this vector and v.
      *
      *
-     * When it is not neccessary to get the exact distance between
+     * When it is not necessary to get the exact distance between
      * two vectors (for example, when simply comparing the
      * two vectors (for example, when simply comparing the
      * distance between different vectors), it is advised to use
      * distance between different vectors), it is advised to use
      * this method instead of distance.
      * this method instead of distance.
@@ -245,7 +245,7 @@ public:
     /**
     /**
      * Returns the squared length of this vector.
      * Returns the squared length of this vector.
      *
      *
-     * When it is not neccessary to get the exact length of a
+     * When it is not necessary to get the exact length of a
      * vector (for example, when simply comparing the lengths of
      * vector (for example, when simply comparing the lengths of
      * different vectors), it is advised to use this method
      * different vectors), it is advised to use this method
      * instead of length.
      * instead of length.