Ver Fonte

Initial check-in for baking COLLADA animations in the encoder.

Darryl Gough há 14 anos atrás
pai
commit
b5c927e04a

+ 216 - 2
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -34,6 +34,217 @@ unsigned int getMaxOffset(domInputLocalOffset_Array& inputArray)
     return maxOffset;
 }
 
+void DAESceneEncoder::bakeAnimations()
+{
+    // Determine the animation frame rate to bake to. (Command line argument? Default 1/24 seconds?)
+
+
+    // Visit all nodes in the scene
+    const domCOLLADA::domSceneRef& domScene = _dom->getScene();
+    daeElement* element = domScene->getInstance_visual_scene()->getUrl().getElement();
+    if (element && element->getElementType() == COLLADA_TYPE::VISUAL_SCENE)
+    {
+        const domVisual_sceneRef visualScene = daeSafeCast<domVisual_scene>(element);
+        const domNode_Array& nodeArray = visualScene->getNode_array();
+        const size_t childCount = nodeArray.getCount();
+        for (size_t i = 0; i < childCount; ++i)
+        {
+            domNodeRef node = nodeArray[i];
+            bakeAnimations(node);
+        }
+    }
+}
+
+static std::string concat(const char* a, const char* b)
+{
+    std::string str;
+    str.reserve(strlen(a) + strlen(b) + 1);
+    str.append(a);
+    str.append(b);
+    return str;
+}
+
+domSource* createInputSource(domAnimationRef& animation, std::vector<float>& keyTimes)
+{
+    domSourceRef inputSource = daeSafeCast<domSource>(animation->createAndPlace("source"));
+    inputSource->setId(concat(animation->getId(), ".matrix-input").c_str());
+
+    domFloat_arrayRef floatArray = daeSafeCast<domFloat_array>(inputSource->createAndPlace("float_array"));
+    floatArray->setId(concat(inputSource->getId(), "-array").c_str());
+    
+    domListOfFloats floats;
+    const size_t count = keyTimes.size();
+    floats.setCount(count);
+    for (size_t i = 0; i < count; ++i)
+    {
+        floats.set(i, keyTimes[i]);
+    }
+    floatArray->setValue(floats);
+    floatArray->setCount(count);
+
+    domSource::domTechnique_commonRef technique = daeSafeCast<domSource::domTechnique_common>(inputSource->createAndPlace("technique_common"));
+    domAccessorRef accessor = daeSafeCast<domAccessor>(technique->createAndPlace("accessor"));
+    accessor->setSource(concat("#", floatArray->getId()).c_str());
+    accessor->setCount(count);
+    accessor->setStride(1);
+    domParamRef param = daeSafeCast<domParam>(accessor->createAndPlace("param"));
+    param->setName("TIME");
+    param->setType("float");
+
+    return inputSource.cast();
+}
+
+domSource* createOutputSource(domAnimationRef& animation, std::vector<Matrix>& matrices)
+{
+    domSourceRef outputSource = daeSafeCast<domSource>(animation->createAndPlace("source"));
+    outputSource->setId(concat(animation->getId(), ".matrix-output").c_str());
+
+    domFloat_arrayRef floatArray = daeSafeCast<domFloat_array>(outputSource->createAndPlace("float_array"));
+    floatArray->setId(concat(outputSource->getId(), "-array").c_str());
+
+    const size_t MATRIX_SIZE = 16;
+    domListOfFloats floats;
+    const size_t floatCount = matrices.size() * MATRIX_SIZE;
+    floats.setCount(floatCount);
+    const size_t matricesCount = matrices.size();
+    for (size_t i = 0; i < matricesCount; ++i)
+    {
+        const Matrix& matrix = matrices[i];
+        size_t index = i * MATRIX_SIZE;
+        floats.set(index, matrix.m[0]);
+        for (size_t j = 0; j < MATRIX_SIZE; ++j)
+        {
+            floats.set(index + j, matrix.m[j]);
+        }
+    }
+    floatArray->setValue(floats);
+    floatArray->setCount(floatCount);
+
+    domSource::domTechnique_commonRef technique = daeSafeCast<domSource::domTechnique_common>(outputSource->createAndPlace("technique_common"));
+    domAccessorRef accessor = daeSafeCast<domAccessor>(technique->createAndPlace("accessor"));
+    accessor->setSource(concat("#", floatArray->getId()).c_str());
+    accessor->setCount(matricesCount);
+    accessor->setStride(MATRIX_SIZE);
+    domParamRef param = daeSafeCast<domParam>(accessor->createAndPlace("param"));
+    param->setName("TRANSFORM");
+    param->setType("float4x4");
+
+    return outputSource.cast();
+}
+
+domSource* createInterpolationSource(domAnimationRef& animation, size_t count)
+{
+    domSourceRef interpolationSource = daeSafeCast<domSource>(animation->createAndPlace("source"));
+    interpolationSource->setId(concat(animation->getId(), ".matrix-interpolation").c_str());
+    
+    domName_arrayRef nameArray = daeSafeCast<domName_array>(interpolationSource->createAndPlace("Name_array"));
+    nameArray->setId(concat(interpolationSource->getId(), "-array").c_str());
+    
+    domListOfNames names;
+    names.setCount(count);
+    for (size_t i = 0; i < count; ++i)
+    {
+        names.set(i, "LINEAR");
+    }
+    nameArray->setValue(names);
+    nameArray->setCount(count);
+
+    domSource::domTechnique_commonRef technique = daeSafeCast<domSource::domTechnique_common>(interpolationSource->createAndPlace("technique_common"));
+    domAccessorRef accessor = daeSafeCast<domAccessor>(technique->createAndPlace("accessor"));
+    accessor->setSource(concat("#", nameArray->getId()).c_str());
+    accessor->setCount(count);
+    accessor->setStride(1);
+    domParamRef param = daeSafeCast<domParam>(accessor->createAndPlace("param"));
+    param->setName("INTERPOLATION");
+    param->setType("name");
+
+    return interpolationSource.cast();
+}
+
+void DAESceneEncoder::bakeAnimations(domNodeRef& node)
+{
+    // find all animation channels that target this node // (getAnimationChannels() in DAEUtil.h?)
+    std::list<domChannelRef> channels;
+    getAnimationChannels(node, channels, false);
+    
+    float bake_frame_rate = 0.04166666667f; // TODO: don't hard code
+    if (!channels.empty())
+    {
+        // find min start time of these channels
+        // find max end time of these channels
+        float startTime = 0, endTime = 0;
+        getStartAndEndTime(channels, &startTime, &endTime);
+        
+        std::vector<float> keyTimes;
+        std::vector<Matrix> matrices;
+        // TODO reserve?
+
+        // create temp container to store key times and key values?
+        for (float t = startTime; t < endTime; t += bake_frame_rate)
+        {
+            Matrix matrix;
+            getNodeTransform(node, channels, t, matrix);
+            //  determine the node's transform at time t from the channels // (will probably have to use Curve.h)
+            //  append(time t, transform)
+            keyTimes.push_back(t);
+            matrices.push_back(matrix);
+        }
+        // determine the node's transform at time end_time
+        // append(time end_time, transform)
+        // 
+
+        // create <animation>
+        domLibrary_animations_Array& animationsLibraryArray = _dom->getLibrary_animations_array();
+        assert(animationsLibraryArray.getCount() > 0);
+        domLibrary_animationsRef& animationsLibrary = animationsLibraryArray.get(0);
+        domAnimationRef animation = daeSafeCast<domAnimation>(animationsLibrary->createAndPlace("animation"));
+        // OpenCOLLADA sets the id equal to the node's ID
+        animation->setId(node->getId());
+
+        // create <source> for key times (INPUT)
+        domSourceRef inputSource = createInputSource(animation, keyTimes);
+
+        // create <source> for key values (OUTPUT)
+        domSourceRef outputSource = createOutputSource(animation, matrices);
+
+        // create <source> for interpolations (INTERPOLATION) (<Name_array>)
+        domSourceRef interpolationSource = createInterpolationSource(animation, keyTimes.size());
+        
+        // create <sampler>
+        domSamplerRef sampler = daeSafeCast<domSampler>(animation->createAndPlace("sampler"));
+        sampler->setId(concat(animation->getId(), "-sampler").c_str());
+        // create <input semantic="INPUT" source="id_of_source">
+        domInputLocalRef inputInput = daeSafeCast<domInputLocal>(sampler->createAndPlace("input"));
+        inputInput->setSemantic("INPUT");
+        inputInput->setSource(concat("#", inputSource->getId()).c_str());
+        // create <input semantic="OUTPUT" source="id_of_source">
+        domInputLocalRef outputInput = daeSafeCast<domInputLocal>(sampler->createAndPlace("input"));
+        outputInput->setSemantic("OUTPUT");
+        outputInput->setSource(concat("#", outputSource->getId()).c_str());
+        // create <input semantic="INTERPOLATION" source="id_of_source">
+        domInputLocalRef interpolationInput = daeSafeCast<domInputLocal>(sampler->createAndPlace("input"));
+        interpolationInput->setSemantic("INTERPOLATION");
+        interpolationInput->setSource(concat("#", interpolationSource->getId()).c_str());
+
+        // create <channel source="source_id" target="node_id/transform" />
+        domChannelRef channel = daeSafeCast<domChannel>(animation->createAndPlace("channel"));
+        channel->setSource(concat("#", sampler->getId()).c_str());
+        channel->setTarget(concat(node->getId(), "/transform").c_str());
+    }
+    // Calculate the baked transform of the Node from <rotate> <scale> <translate> and delete those elements.
+    // Add <matrix> element to the node with the calculated transform matrix. Make sure sid="transform". // Matches <channel target="node_id/transform">
+    bakeNodeTransform(node);
+
+    // visit all child nodes
+    const domNode_Array& nodeArray = node->getNode_array();
+    const size_t childCount = nodeArray.getCount();
+    for (size_t i = 0; i < childCount; ++i)
+    {
+        domNodeRef node = nodeArray[i];
+        bakeAnimations(node);
+    }
+}
+
 void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLLADA* dom)
 {
     DAEOptimizer optimizer(dom);
@@ -50,6 +261,9 @@ void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLL
         }
         end("groupAnimation");
     }
+
+    bakeAnimations();
+
     if (arguments.DAEOutputEnabled())
     {
         if (!_collada->writeTo(arguments.getFilePath(), arguments.getDAEOutputPath()))
@@ -813,9 +1027,9 @@ void DAESceneEncoder::transformNode(domNode* domNode, Node* node)
 
 void DAESceneEncoder::calcTransform(domNode* domNode, Matrix& dstTransform)
 {
-    daeTArray<daeSmartRef<daeElement> > children;
+    daeTArray<daeSmartRef<daeElement>> children;
     domNode->getChildren(children);
-    size_t childCount = children.getCount();
+    const size_t childCount = children.getCount();
     for (size_t i = 0; i < childCount; ++i)
     {
         daeElementRef childElement = children[i];

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

@@ -81,6 +81,9 @@ private:
         }
     };
 
+    void bakeAnimations();
+    void bakeAnimations(domNodeRef& node);
+
     /**
      * Optimizes the COLLADA dom based on the arguments passed to the encoder.
      * 

+ 208 - 10
gameplay-encoder/src/DAEUtil.cpp

@@ -1,6 +1,8 @@
 #include "Base.h"
 #include "DAEUtil.h"
 #include "StringUtil.h"
+#include "DAEChannelTarget.h"
+#include "Vector4.h"
 
 namespace gameplay
 {
@@ -15,7 +17,7 @@ namespace gameplay
  */
 int getIndex(const domInstance_controller::domSkeleton_Array& skeletonArray, const domNodeRef& node);
 
-void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& channels)
+void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& channels, bool recursive)
 {
     assert(node->getId());
     std::string nodeIdSlash (node->getId());
@@ -49,17 +51,20 @@ void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& chan
         }
     }
 
-    // Recursively do the same for all nodes
-    daeTArray< daeSmartRef<daeElement> > children;
-    node->getChildren(children);
-    size_t childCount = children.getCount();
-    for (size_t i = 0; i < childCount; ++i)
+    if (recursive)
     {
-        daeElementRef childElement = children[i];
-        if (childElement->getElementType() == COLLADA_TYPE::NODE)
+        // Recursively do the same for all nodes
+        daeTArray< daeSmartRef<daeElement>> children;
+        node->getChildren(children);
+        size_t childCount = children.getCount();
+        for (size_t i = 0; i < childCount; ++i)
         {
-            domNodeRef childNode = daeSafeCast<domNode>(childElement);
-            getAnimationChannels(childNode, channels);
+            daeElementRef childElement = children[i];
+            if (childElement->getElementType() == COLLADA_TYPE::NODE)
+            {
+                domNodeRef childNode = daeSafeCast<domNode>(childElement);
+                getAnimationChannels(childNode, channels);
+            }
         }
     }
 }
@@ -262,6 +267,199 @@ const domInstance_controller::domSkeletonRef getSkeleton(const domInstance_contr
     return NULL;
 }
 
+void getStartAndEndTime(const domChannelRef& channel, float* startTime, float* endTime)
+{
+    domSourceRef source = getInputSource(channel);
+    const domFloat_arrayRef floatArray = source->getFloat_array();
+    const domListOfFloats& floatList = floatArray->getValue();
+    assert(floatList.getCount() > 0);
+    *startTime = (float)floatList.get(0);
+    *endTime = (float)floatList.get(floatList.getCount() - 1);
+}
+
+void getStartAndEndTime(const std::list<domChannelRef>& channels, float* startTime, float* endTime)
+{
+    float minStart = FLT_MAX, maxEnd = 0, start = FLT_MAX, end = 0;
+    for (std::list<domChannelRef>::const_iterator i = channels.begin(); i != channels.end(); ++i)
+    {
+        getStartAndEndTime(*i, &start, &end);
+        minStart = std::min(start, minStart);
+        maxEnd = std::max(end, maxEnd);
+    }
+    assert(minStart <= maxEnd);
+    *startTime = minStart;
+    *endTime = maxEnd;
+}
+
+DAEChannelTarget* getChannelTarget(std::list<DAEChannelTarget>& targets, const char* sid)
+{
+    if (!sid)
+        return NULL;
+
+    for (std::list<DAEChannelTarget>::const_iterator i = targets.begin(); i != targets.end(); ++i)
+    {
+        
+    }
+    return NULL;
+}
+
+void getRotate(Vector4& rotate, float time)
+{
+    // if property is ANGLE
+
+}
+
+void bakeNodeTransform(domNodeRef& node)
+{
+    Matrix nodeBakedMatrix;
+
+    daeTArray<daeSmartRef<daeElement>> children;
+    node->getChildren(children);
+    size_t childCount = children.getCount();
+    for (size_t i = 0; i < childCount; ++i)
+    {
+        daeElementRef childElement = children[i];
+        switch (childElement->getElementType())
+        {
+            case COLLADA_TYPE::TRANSLATE:
+                {
+                    domTranslateRef translateNode = daeSafeCast<domTranslate>(childElement);
+                    float x = (float)translateNode->getValue().get(0);
+                    float y = (float)translateNode->getValue().get(1);
+                    float z = (float)translateNode->getValue().get(2);
+                    nodeBakedMatrix.translate(x, y, z);
+                    break;
+                }
+            case COLLADA_TYPE::ROTATE:
+                {
+                    domRotateRef rotateNode = daeSafeCast<domRotate>(childElement);
+                    Vector4 rotate;
+                    rotate.x = (float)rotateNode->getValue().get(0);
+                    rotate.y = (float)rotateNode->getValue().get(1);
+                    rotate.z = (float)rotateNode->getValue().get(2);
+                    rotate.w = MATH_DEG_TO_RAD((float)rotateNode->getValue().get(3)); // COLLADA uses degrees, gameplay uses radians
+                    if (rotate.w != 0.0f)
+                    {
+                        nodeBakedMatrix.rotate(rotate.x, rotate.y, rotate.z, rotate.w);
+                    }
+                    break;
+                }
+            case COLLADA_TYPE::SCALE:
+                {
+                    domScaleRef scaleNode = daeSafeCast<domScale>(childElement);
+                    float x = (float)scaleNode->getValue().get(0);
+                    float y = (float)scaleNode->getValue().get(1);
+                    float z = (float)scaleNode->getValue().get(2);
+                    nodeBakedMatrix.scale(x, y, z);
+                    break;
+                }
+            default:
+                break;
+        }
+    }
+    // go through the list backwards because elements will be removed
+    for (int i = childCount - 1; i >= 0; --i)
+    {
+        daeElementRef childElement = children[i];
+        switch (childElement->getElementType())
+        {
+            case COLLADA_TYPE::TRANSLATE:
+            case COLLADA_TYPE::ROTATE:
+            case COLLADA_TYPE::SCALE:
+            case COLLADA_TYPE::EXTRA:
+                daeElement::removeFromParent(childElement);
+            default:
+                break;
+        }
+    }
+
+    domMatrixRef mat = daeSafeCast<domMatrix>(node->createAndPlaceAt(0, "matrix"));
+    mat->setSid("transform");
+    domFloat4x4 floatMatrix;
+    floatMatrix.setCount(16);
+    floatMatrix.set(0, nodeBakedMatrix.m[0]);
+    floatMatrix.set(1, nodeBakedMatrix.m[4]);
+    floatMatrix.set(2, nodeBakedMatrix.m[8]);
+    floatMatrix.set(3, nodeBakedMatrix.m[12]);
+    floatMatrix.set(4, nodeBakedMatrix.m[1]);
+    floatMatrix.set(5, nodeBakedMatrix.m[5]);
+    floatMatrix.set(6, nodeBakedMatrix.m[9]);
+    floatMatrix.set(7, nodeBakedMatrix.m[13]);
+    floatMatrix.set(8, nodeBakedMatrix.m[2]);
+    floatMatrix.set(9, nodeBakedMatrix.m[6]);
+    floatMatrix.set(10, nodeBakedMatrix.m[10]);
+    floatMatrix.set(11, nodeBakedMatrix.m[14]);
+    floatMatrix.set(12, nodeBakedMatrix.m[3]);
+    floatMatrix.set(13, nodeBakedMatrix.m[7]);
+    floatMatrix.set(14, nodeBakedMatrix.m[11]);
+    floatMatrix.set(15, nodeBakedMatrix.m[15]);
+    mat->setValue(floatMatrix);
+}
+
+void getNodeTransform(const domNodeRef& node, std::list<domChannelRef>& channels, float time, Matrix& dstTransform)
+{
+    // for each transform element in node
+    // modify the transform element if one (or more?) of the channels targets it
+    // bake the transform
+
+    std::list<DAEChannelTarget> targets;
+
+    //DAEChannelTarget channelTarget(
+    for (std::list<domChannelRef>::const_iterator i = channels.begin(); i != channels.end(); ++i)
+    {
+        targets.push_back(DAEChannelTarget(*i));
+    }
+    // TODO sort targets?
+
+    daeTArray<daeSmartRef<daeElement>> children;
+    node->getChildren(children);
+    size_t childCount = children.getCount();
+    for (size_t i = 0; i < childCount; ++i)
+    {
+        daeElementRef childElement = children[i];
+        switch (childElement->getElementType())
+        {
+            case COLLADA_TYPE::TRANSLATE:
+                {
+                    domTranslateRef translateNode = daeSafeCast<domTranslate>(childElement);
+                    float x = (float)translateNode->getValue().get(0);
+                    float y = (float)translateNode->getValue().get(1);
+                    float z = (float)translateNode->getValue().get(2);
+                    dstTransform.translate(x, y, z);
+                    break;
+                }
+            case COLLADA_TYPE::ROTATE:
+                {
+                    domRotateRef rotateNode = daeSafeCast<domRotate>(childElement);
+                    //rotateNode->getSid();
+                    Vector4 rotate;
+                    rotate.x = (float)rotateNode->getValue().get(0);
+                    rotate.y = (float)rotateNode->getValue().get(1);
+                    rotate.z = (float)rotateNode->getValue().get(2);
+                    rotate.w = MATH_DEG_TO_RAD((float)rotateNode->getValue().get(3)); // COLLADA uses degrees, gameplay uses radians
+
+                    if (rotate.w != 0.0f)
+                    {
+                        dstTransform.rotate(rotate.x, rotate.y, rotate.z, rotate.w);
+                    }
+                    break;
+                }
+            case COLLADA_TYPE::SCALE:
+                {
+                    domScaleRef scaleNode = daeSafeCast<domScale>(childElement);
+                    float x = (float)scaleNode->getValue().get(0);
+                    float y = (float)scaleNode->getValue().get(1);
+                    float z = (float)scaleNode->getValue().get(2);
+                    dstTransform.scale(x, y, z);
+                    break;
+                }
+            default:
+                break;
+        }
+
+    }
+}
+
 bool equalKeyTimes(const domSource* s1, const domSource* s2)
 {
     // TODO: shouldn't assume that the source has a float array.

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

@@ -1,6 +1,8 @@
 #ifndef DAEUTIL_H_
 #define DAEUTIL_H_
 
+#include "Matrix.h"
+
 namespace gameplay
 {
 
@@ -9,8 +11,9 @@ namespace gameplay
  * 
  * @param node The node that the animation channels target.
  * @param channels The list of channels to append to.
+ * @param recursive True if channels that target child nodes should also be included.
  */
-void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& channels);
+void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& channels, bool recursive = true);
 
 /**
  * Gets the joint names for the given source and appends them to the given list.
@@ -66,6 +69,13 @@ const domSourceRef getSource(const domInputLocalRef& inputLocal, const domAnimat
  */
 const domName_arrayRef getSourceNameArray(const domSourceRef& source);
 
+void bakeNodeTransform(domNodeRef& node);
+
+/**
+ * Gets the node's transform at the given time.
+ */
+void getNodeTransform(const domNodeRef& node, std::list<domChannelRef>& channels, float time, Matrix& dstTransform);
+
 /**
  * Returns one skeleton from the given instance controller.
  * The COLLADA spec says that instance_controller can have multiple skeletons but we only need one.
@@ -77,6 +87,17 @@ const domName_arrayRef getSourceNameArray(const domSourceRef& source);
  */
 const domInstance_controller::domSkeletonRef getSkeleton(const domInstance_controllerRef& instanceController);
 
+/**
+ * Gets the start and end time for the given animation channel.
+ * 
+ * @param channel The channel.
+ * @param startTime The start time returned.
+ * @param endTime The end time returned.
+ */
+void getStartAndEndTime(const domChannelRef& channel, float* startTime, float* endTime);
+
+void getStartAndEndTime(const std::list<domChannelRef>& channels, float* startTime, float* endTime);
+
 /**
  * Returns true if the two given animation channels have equal key time input source.
  *