Advertisement

FBX SDK skinned animation

Started by September 04, 2018 07:44 PM
39 comments, last by Dirk Gregorius 5 years, 10 months ago
On 9/8/2018 at 5:25 PM, Dirk Gregorius said:

At run-time you now can create poses for the skeleton. In a first step I would simply animate and render the skeleton. That should be pretty simple, but will give you confidence your data is correct. The next step is then binding the skeleton to your mesh. This is called skinning. The skinning information is stored in so called FbxCluster which you get from the FbxMesh. The simplest approach to deform the mesh using skinning is something like this:

Using spheres I was able to create and animate a generalized "skeleton" to visualize skeletal data from fbx files. But, now when I go to skin the mesh it moves but it all blows up on me. I've gotten and stored the bone space joint animation matricies per frame and multiplied them by the inverse bind pose of the same joint, but to no avail. My shader code for the gbufferAnim is similar to the ogldev tutorial 38 code for assimp. Does anyone think I'm missing something critical  here to narrow it down?

2wpqu6c.jpg


 


layout(location = 0) in vec3 pE;
layout(location = 1) in vec2 uvE;
layout(location = 2) in vec3 tE;
layout(location = 3) in vec3 nE;
layout(location = 4) in ivec4 boneIdE;
layout(location = 5) in vec4 boneWtE;

const int MAX_BONES = 100;
uniform mat4 gBones[MAX_BONES];

mat4 BoneTransform       = gBones[boneIdE[0]] * boneWtE[0];
BoneTransform     += gBones[boneIdE[1]] * boneWtE[1];
BoneTransform     += gBones[boneIdE[2]] * boneWtE[2];
BoneTransform     += gBones[boneIdE[3]] * boneWtE[3];

gl_Position = MVP * BoneTransform * vec4(pE, 1.f);

 

 

 

I've perhaps narrowed the problem down to not transposing the output per frame animation matricies, but I've tried combinations of each one without success. How do I know I need to transpose a particular matrix? Do I need to transpose the joint matrix * the parent matrix

This is a combination of T * R interpolated matricies multiplied by the parent matrix and then multiplied by the inverse bind pose.

So I have animatedxform = some combination of ParentXform * T * R * invbindpose

Advertisement

In a first step I would simply export the FbxClusters and use them to deform the mesh on the CPU as I showed you in an earlier post. Once you have this working you can implement the GPU version. 

3 hours ago, Dirk Gregorius said:

In a first step I would simply export the FbxClusters and use them to deform the mesh on the CPU as I showed you in an earlier post. Once you have this working you can implement the GPU version. 

I saw your code but I used TLang's extended article to get me to where I am with the skeleton working. Now to go back to CPU I dont see the point since my mesh is already heavily simplified. Again, I'm just following his published article.

What you propose is to what exactly? Write individual poses onto my XML file and then read them back and then load them onto the GPU in a sequence instead of what I'm doing now which is...traditional gpu skinning? I'm sorry, the FBX terminology is confusing, and according to TLang a " Each cluster is and is not a joint...... You can see a cluster as a joint, but actually, inside each cluster, there is a "link". This "link" is actually the real joint,"

No, I am suggesting that you transform your mesh vertices on the CPU and then render the transformed mesh. Similar to to what you did for the skeleton. Basically I am suggesting for you to take smaller steps so succeeding in solving your problem becomes easier. 

 

On 9/19/2018 at 5:16 PM, Dirk Gregorius said:

No, I am suggesting that you transform your mesh vertices on the CPU and then render the transformed mesh. Similar to to what you did for the skeleton. Basically I am suggesting for you to take smaller steps so succeeding in solving your problem becomes easier. 

I'm just confused about what you're talking about and I've gotten lost. This is the last big road block for my gamedev endevours. Is it possible for you to write a little more code for the CPU skinning side of things to get me going with the debug? I'm sorry to be such a noob at this, but i just cant see the light at the end of the tunnel if you know what I mean.

 

Also, what did you mean by this on page 1? Could you explain this 

                // DON'T get the geometric transform here - it does not propagate! Bake it into the mesh instead! This makes it also easier to read the skinning from the clusters!

 // Export your mesh and bake the geometric transform into the vertex attributes! 

 

Also, your ReadAnimation function (second one) could you clarify that that is the final animation (skinning) matrix? I always read that I need to apply a inverse bindpose of the joint to that, but you dont specify how to get that exactly although I've asked you once before in this thread and I wasnt able to deduce what you meant. So, if yo could clarify that as well, if and how to get the bindpose for the joint to go along with your ReadAnimation() function that would be great. 
 

Advertisement

No problem! I will try to help. Let's talk about the basic algorithm first, this should give you an idea what information/data you need. This should make it much easier for you to navigate the FBX file and retrieve the data you need. Finally we can talk about implementation details and common pitfalls and mistakes.

There are four principal data elements

1) A transform hierarchy (skeleton)

2) An animation clip  

3) One or more meshes

4) A binding for each mesh to the skeleton (cluster)

The first thing you need is the skeleton. I already posted some code how to retrieve the skeleton, but let me know if you need more detail. There are two ways to describe a skeleton: hierarchical or linear. The hierarchical representation is a classic tree structure and could look something like this:


struct Bone
{
  Vector Translation;
  Quaternion Rotation;
  
  Matrix4x4 RelativeTransform;
  Matrix4x4 AbsoluteTransform;
  
  Matrix4x4 BindPose;
  
  Bone* Parent;
  std::vector< Bone* > Children;
};


struct Skeleton
{
  Bone* Root;
};

The second option is to 'linearize' the transform hierarchy. This is usually done by traversing in DFS or BFS order. This assures that the parent bone is located before all children in the array. The linear representation can look something like this:


struct Transform
{
  Vector Translation;
  Quaternion Rotation;
};

struct Skeleton
{
  int BoneCount;
  std::vector< std::string > BoneNames;
  std::vector< int > BoneParents;
  std::vector< Transform > BoneBindPoses;
};

struct Pose
{
  Skeleton* Skeleton;
  
  std::vector< Transform > RelativeTransforms;
  std::vector< Transform > AbsoluteTransforms;
};

Note that I skipped scale as this would only unnecessarily make things more complicated here.

The next thing is the animation clip which you sample at each frame to retrieve a pose for the skeleton. A simple animation structure looks like this


struct Animation
{
  int FrameCount;
  float FrameRate;

  std::vector< Vector > TranslationKeys;
  std::vector< Quaternion > RotationKeys;
};

So each frame you get translation and rotation of each bone and write it into Bone structure (hierarchical representation) or better you just extract the current Pose (linear representation). 

The next step is to deform the mesh using the new skeleton pose. This is where the skeleton/mesh binding comes in. This binding is often referred to as skin and it tells you how much a bone influences a vertex of the mesh. Again there are two ways to describe the data based on the association. E.g. either each bone knows which vertices it deforms or each vertex knows by which bone it is deformed. This looks something like this:


struct Cluster
{
  Bone* Bone;
	
  std::vector< int > VertexIndices;
  std::vector< float > VertexWeight;	
};


#define MAX_BONE_COUNT 4
  
struct Vertex
{
  Vector Position;
  Vector Normal;
  // ...
  
  int BoneIndex[ MAX_BONE_COUNT ];
  float BoneWeight[ MAX_BONE_COUNT ];
};

The final step is to apply the new pose to the mesh. For the cluster this looks something like this:


std::vector< Vector > DeformMeshPositions( int ClusterCount, const Cluster* Clusters, const Mesh* Mesh )
{
  std::vector< Vector > VertexPositions;
  VertexPositions.resize( Mesh->VertexCount );
  std::fill( VertexPositions.begin(), VertexPositions.end(), Vector::Zero );
                         
  for ( int i = 0; i < ClusterCount; ++i )
  {
    const Cluster* Cluster = Clusters[ i ];
    
    const Bone* Bone = Cluster->Bone;
    Matrix4x4 BoneTransform = Bone->AbsoluteTransform;
    Matrix4x4 BindPose = Bone->BindPose;
    Matrix4x4 Transform = BoneTransform * Inverse( BindPose );
   
    for ( int k = 0; k < Cluster->VertexCount; ++k )
    {
     int VertexIndex = Cluster->VertexIndex[ k ];
     int VertexWeight = Cluster->VertexWeight[ k ];
      
     VertexPositions[ VertexIndex ] += VertexWeight * Transform * Mesh->VertexPositions[ VertexIndex ];
    }
  }
                         
  return VertexPositions;  
};

The algorithm takes a vertex in model space into the local space of the bone at bind time. Then it moves to the new location of the bone and transforms it back to model space. Think of it as if you are attaching the vertex to the bone, then move the bone, and finally detach the vertex and release it at the new location. Finally apply a weight to this new vertex position and sum it up. 

HTH, 

-Dirk  

 

PS:

IIRC the geometric transform is a concept from 3D MAX. It is a special transform which is applied to the associated shapes of a node, but it is not propagated down the hierarchy. This means you cannot just bake it into your bone transform. The simplest way to deal with it is to ignore it when retrieving the skeleton and then apply it to the mesh vertices when you read them. I look up some code and post it here. It is really simple.

21 hours ago, Dirk Gregorius said:

The second option is to 'linearize' the transform hierarchy. This is usually done by traversing in DFS or BFS order. This assures that the parent bone is located before all children in the array. The linear representation can look something like this:



struct Transform
{
  Vector Translation;
  Quaternion Rotation;
};

struct Skeleton
{
  int BoneCount;
  std::vector< std::string > BoneNames;
  std::vector< int > BoneParents;
  std::vector< Transform > BoneBindPoses;
};

struct Pose
{
  Skeleton* Skeleton;
  
  std::vector< Transform > RelativeTransforms;
  std::vector< Transform > AbsoluteTransforms;
};

The next step is to deform the mesh using the new skeleton pose. This is where the skeleton/mesh binding comes in. This binding is often referred to as skin and it tells you how much a bone influences a vertex of the mesh. Again there are two ways to describe the data based on the association. E.g. either each bone knows which vertices it deforms or each vertex knows by which bone it is deformed. This looks something like this:

The final step is to apply the new pose to the mesh. For the cluster this looks something like this:



std::vector< Vector > DeformMeshPositions( int ClusterCount, const Cluster* Clusters, const Mesh* Mesh )
{
  std::vector< Vector > VertexPositions;
  VertexPositions.resize( Mesh->VertexCount );
  std::fill( VertexPositions.begin(), VertexPositions.end(), Vector::Zero );
                         
  for ( int i = 0; i < ClusterCount; ++i )
  {
    const Cluster* Cluster = Clusters[ i ];
    
    const Bone* Bone = Cluster->Bone;
    Matrix4x4 BoneTransform = Bone->AbsoluteTransform;
    Matrix4x4 BindPose = Bone->BindPose;
    Matrix4x4 Transform = BoneTransform * Inverse( BindPose );
   
    for ( int k = 0; k < Cluster->VertexCount; ++k )
    {
     int VertexIndex = Cluster->VertexIndex[ k ];
     int VertexWeight = Cluster->VertexWeight[ k ];
      
     VertexPositions[ VertexIndex ] += VertexWeight * Transform * Mesh->VertexPositions[ VertexIndex ];
    }
  }
                         
  return VertexPositions;  
};

The algorithm takes a vertex in model space into the local space of the bone at bind time. Then it moves to the new location of the bone and transforms it back to model space. Think of it as if you are attaching the vertex to the bone, then move the bone, and finally detach the vertex and release it at the new location. Finally apply a weight to this new vertex position and sum it up. 

 

What you have on the last bit of code is CPU skinning, yes? Could you go into that in a bit more detail...when you say std::vector<Vector> I  assume you mean vector4 and are doing the std::Fill with the mesh position vec4 elements?

Here is some snippets of my code. I'm making progress but putting your Read() function in has made my skeletal debug not function properly. I assume I need to switch and store both local and absolute coordinates? Looking at my code below in that section, how would that be added? From this picture of the skinning on the mesh you can see that it is squished lengthwise and doesnt deform correctly.

b8tlzl.jpg

This is what I have:

//get skeleton hierarchy with DFS
//get inverse bindpose for joint
//joints are associated with control points
//get keyframe poses with your function based on readAnimation
//generate / write out keyframe poses with your function 'readanimation' to XML
//read from XML file
//calculate skinning interpolation
//send to gbuffer animation shader


struct Joint
{
    string mName;
    int mParentIndex;
    FbxAMatrix mGlobalBindposeInverse;
    FbxAMatrix mGlobalBindpose;
    FbxNode *mNode;
};

ProcessSkeletonHierarchy(mScene->GetRootNode());

void FBXtoAbj::ProcessSkeletonHierarchy(FbxNode *inRootNode)
{
    cout << "inRootNode->GetChildCount() = " << inRootNode->GetChildCount() << endl;

    for (int i = 0; i < inRootNode->GetChildCount(); ++i)
    {
        FbxNode *currNode = inRootNode->GetChild(i);
        ProcessSkeletonHierarchyRecursively(currNode, 0, -1);
    }
}

void FBXtoAbj::ProcessSkeletonHierarchyRecursively(FbxNode *inNode, int myIndex, int inParentIndex)
{
    if (inNode->GetNodeAttribute() && inNode->GetNodeAttribute()->GetAttributeType() && inNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eSkeleton)
    {
        Joint currJoint;
        currJoint.mParentIndex = inParentIndex;
        currJoint.mName = inNode->GetName();
        mJoints.push_back(currJoint);

        allJointsOnFBX.push_back(inNode);
    }

    for (int i = 0; i < inNode->GetChildCount(); ++i)
        ProcessSkeletonHierarchyRecursively(inNode->GetChild(i), int(mJoints.size()), myIndex);
}

//GET INVERSE BINDPOSE FOR JOINT / JOINTS ARE ASSOCIATED WITH CONTROL POINTS


ProcessJointsAndAnimations(inNode);
ProcessMesh(inNode);

void FBXtoAbj::ProcessJointsAndAnimations(FbxNode *inNode)
{
    FbxMesh *currMesh = inNode->GetMesh();

    uint numDeformers = currMesh->GetDeformerCount();
    for (uint i = 0; i < numDeformers; ++i)
    {
        FbxSkin *currSkin = reinterpret_cast<FbxSkin *>(currMesh->GetDeformer(i, FbxDeformer::eSkin));

        if (!currSkin)
            continue;

        uint numClusters = currSkin->GetClusterCount();

        for (uint j = 0; j < numClusters; ++j)
        {
            FbxCluster *currCluster = currSkin->GetCluster(j);
            string currJointName = currCluster->GetLink()->GetName();
            uint currJointIndex = FindJointIndexUsingName(currJointName);

            FbxAMatrix transformMatrix, transformLinkMatrix;
            FbxAMatrix geometryTransform = GetGeometryTransformation(inNode);
            //geometryTransform.SetIdentity();

            currCluster->GetTransformMatrix(transformMatrix); //the xform of the mesh at binding time
            currCluster->GetTransformLinkMatrix(transformLinkMatrix); // the xform of the cluster(jnt) at binding time from joint space to world space
            mJoints[currJointIndex].mGlobalBindpose = transformLinkMatrix * transformMatrix * geometryTransform;
            mJoints[currJointIndex].mGlobalBindposeInverse = transformLinkMatrix.Inverse() * transformMatrix * geometryTransform;

            //JOINTS ARE ASSOCIATED WITH CONTROL POINTS
            uint numIndices = currCluster->GetControlPointIndicesCount();

            for (uint k = 0; k < numIndices; ++k)
            {
                BlendingIndexWeightPair currBlendingIndexWeightPair;
                currBlendingIndexWeightPair.mBlendingIndex = currJointIndex;
                currBlendingIndexWeightPair.mBlendingWeight = currCluster->GetControlPointWeights()[k];

                mControlPoints[currCluster->GetControlPointIndices()[k]]->mBlendingInfo.push_back(currBlendingIndexWeightPair);
            }

        }
    }
}

struct VertexBlendingInfo
{
    uint mBlendingIndex;
    double mBlendingWeight;

    bool operator < (const VertexBlendingInfo &rhs)
    {
        return (mBlendingWeight > rhs.mBlendingWeight);
    }
};

void FBXtoAbj::ProcessMesh(FbxNode *inNode)
{
    ../
        PNTIWVertex temp;
    temp.mPosition = currCtrlPoint->mPosition;
    temp.mUV = UV[j][0];
    temp.mTangent = tangent[j];
    temp.mNormal = normal[j];

    for (uint k = 0; k < currCtrlPoint->mBlendingInfo.size(); ++k)
    {
        VertexBlendingInfo currBlendingInfo;
        currBlendingInfo.mBlendingWeight = currCtrlPoint->mBlendingInfo[k].mBlendingWeight;
        currBlendingInfo.mBlendingIndex = currCtrlPoint->mBlendingInfo[k].mBlendingIndex;
        temp.mVertexBlendingInfos.push_back(currBlendingInfo);
    } 

    temp.SortBlendingInfoByWeight();
}

struct PNTIWVertex
{
    glm::vec3 mPosition, mNormal, mTangent;
    glm::vec2 mUV;
    vector<VertexBlendingInfo> mVertexBlendingInfos;

    void SortBlendingInfoByWeight()
    {
        sort(mVertexBlendingInfos.begin(), mVertexBlendingInfos.end());
    }

    bool operator == (PNTIWVertex &rhs)
    {
        bool sameBlendingInfo = 1;

        //only compare the blending infos when there is any
        if (!(mVertexBlendingInfos.empty() && rhs.mVertexBlendingInfos.empty()))
        {
            //each vert should have 4 index-weight blending info pairs because games typically distribute weights to 4 joints
            for (uint i = 0; i < 4; ++i)
            {
                if (mVertexBlendingInfos[i].mBlendingIndex != rhs.mVertexBlendingInfos[i].mBlendingIndex ||
                    abs(mVertexBlendingInfos[i].mBlendingWeight - rhs.mVertexBlendingInfos[i].mBlendingWeight) > .001
                    )
                {
                    sameBlendingInfo = 0;
                    break;
                }
            }
        }

        bool result0 = (mPosition == rhs.mPosition);
        bool result1 = (mNormal == rhs.mNormal);
        bool result2 = (mUV == rhs.mUV);
        bool result3 = (mTangent == rhs.mTangent);

        return result0 && result1 && result2 && result3 && sameBlendingInfo;
    }
};


//GENERATE / WRITE OUT KEYFRAME POSES WITH YOUR FUNCTION 'READ' TO XML


void FBXtoAbj::WriteAnimationToStream(ostream& inStream)
{
    inStream << fixed << setprecision(3);
    
    inStream << "\t\t<animation name='" << mAnimationName << "' length='" << mAnimationLength << "' numJoints='" << mJoints.size() << "'>\n";

    for (uint i = 0; i < mJoints.size(); ++i)
    {
        string usableParentName = (i == 0) ? "ROOT" : mJoints[mJoints[i].mParentIndex].mName;

        //inStream << "\t\t\t<track id='" << i << "' name='" << mJoints[i].mName << "'>\n";
        inStream << "\t\t\t<track id='" << i << "' name='" << mJoints[i].mName << "' parent='" << usableParentName << "'>\n";

        FbxTime::SetGlobalTimeMode(FbxTime::eFrames24);
        FbxAnimStack *currAnimStack = mScene->GetSrcObject<FbxAnimStack>(0);

        mScene->SetCurrentAnimationStack(currAnimStack);
        FbxString Name = currAnimStack->GetNameOnly();
        FbxString TakeName = currAnimStack->GetName();
        FbxTakeInfo* TakeInfo = mScene->GetTakeInfo(TakeName);
        FbxTimeSpan LocalTimeSpan = TakeInfo->mLocalTimeSpan;
        FbxTime Start = LocalTimeSpan.GetStart();
        FbxTime Stop = LocalTimeSpan.GetStop();
        FbxTime Duration = LocalTimeSpan.GetDuration();

        FbxTime::EMode TimeMode = FbxTime::GetGlobalTimeMode();
        FbxLongLong FrameCount = Duration.GetFrameCount(TimeMode);
        double FrameRate = FbxTime::GetFrameRate(TimeMode);

        for (FbxLongLong f = Start.GetFrameCount(TimeMode); f <= Stop.GetFrameCount(TimeMode); ++f)
        {
            FbxTime Time;
            Time.SetFrame(f, TimeMode);

            for (FbxNode *Node : allJointsOnFBX)
            {
                if (Node->GetName() == mJoints[i].mName)
                {
                    inStream << "\t\t\t\t <frame num='" << f << "'>\n";

                    FbxAMatrix LocalTransform = Node->EvaluateGlobalTransform(Time);

                    //is there a parent bone? If so, need to apply that parents global inverse transform to this node's transform

                    if (FbxNode *Parent = Node->GetParent())
                    {
                        FbxNodeAttribute *ParentAttribute = Parent->GetNodeAttribute();

                        if (ParentAttribute && ParentAttribute->GetAttributeType() == FbxNodeAttribute::eSkeleton)
                        {
                            FbxAMatrix GlobalParentTransform = Parent->EvaluateGlobalTransform(Time);
                            LocalTransform = GlobalParentTransform.Inverse() * LocalTransform;
                        }

                        FbxVector4 Translation(LocalTransform.GetT());
                        inStream << "\t\t\t\t\t";
                        inStream << "<decompT>" << static_cast<float>(Translation.mData[0]) << "," << static_cast<float>(Translation.mData[1]) << "," << static_cast<float>(Translation.mData[2]) << "</decompT>\n";

                        FbxQuaternion Rotation(LocalTransform.GetQ());
                        inStream << "\t\t\t\t\t";
                        inStream << "<decompR>" << static_cast<float>(Rotation.mData[0]) << "," << static_cast<float>(Rotation.mData[1]) << "," << static_cast<float>(Rotation.mData[2]) << "," << static_cast<float>(Rotation.mData[3]) << "</decompR>\n";
                    }
                    inStream << "\t\t\t\t </frame>\n";
                }
            }
        }
        inStream << "\t\t\t </track>\n";
    }
    inStream << "\t\t</animation>\n";
}

//read from XML file
//calculate skinning interpolation
//apply pose to mesh


ApplyPose(AnimationTime, myAbj.allAnims[0].trackData[0], glm::mat4(1.f)); // 
void Object::ApplyPose(float AnimationTime, TrackData track, const glm::mat4 &ParentTransform)
{
    glm::quat RotationQ;
    CalcInterpolatedRotationGLM_solo(AnimationTime, track, RotationQ);
    glm::mat4 RotationM = glm::toMat4(RotationQ);

    glm::vec3 Translation;
    CalcInterpolatedPositionGLM_solo(AnimationTime, track, Translation);
    glm::mat4 TranslationM = glm::translate(glm::mat4(1.f), Translation);

//    // Combine the above transformations
    glm::mat4 NodeTransform = TranslationM * RotationM;

    for (int i = 0; i < myAbj.allBindSkeletons[0].skeletonCt; ++i)
    {
        if (myAbj.allBindSkeletons[0].skeletonData[i].name == track.name)
        {
            string gBonesName;
            gBonesName.append("gBones[");
            gBonesName.append(to_string(track.id));
            gBonesName.append("]");

            NameGBones2 xformNameCombo;

            xformNameCombo.animatedXform = ParentTransform * NodeTransform * myAbj.allBindSkeletons[0].skeletonData[i].inverseBindpose;

            //xformNameCombo.animatedXform = glm::mat4(1.f);
            xformNameCombo.name = gBonesName;
            xformNameCombo.nameMesh = name->val_s;

            myAbj.aiGbones2.push_back(xformNameCombo);
        }
    } 

    for (auto &i : myAbj.allAnims[0].trackData)
    {
        if (i.parent == track.name)
        {
            //cout << "found parent / child for : " << track.name << " " << i.name << endl;
            ApplyPose(AnimationTime, i, ParentTransform * NodeTransform);
        }
    }
}

 

1 hour ago, mrMatrix said:

What you have on the last bit of code is CPU skinning, yes? Could you go into that in a bit more detail...when you say std::vector<Vector> I  assume you mean vector4 and are doing the std::Fill with the mesh position vec4 elements?

Vector is a 3D vector and the std:fill initializes them to zero. This is so I can correctly accumulate.

I need to search through my repository to find the FBX cluster and mesh extraction code. Then I will post it here, but this might take until tomorrow. Sorry for the delay, I switched to exporting directly from Maya since I added ragdolls and cloth and wrote my own plug-ins. It didn't make sense to use FBX for me anymore. I need to dig it up if I find time later today...

2 hours ago, Dirk Gregorius said:

Vector is a 3D vector and the std:fill initializes them to zero. This is so I can correctly accumulate.

I need to search through my repository to find the FBX cluster and mesh extraction code. Then I will post it here, but this might take until tomorrow. Sorry for the delay, I switched to exporting directly from Maya since I added ragdolls and cloth and wrote my own plug-ins. It didn't make sense to use FBX for me anymore. I need to dig it up if I find time later today...

Thank you and take your time .

 

The skeletal problem was """fixed""" by putting both the old code and your 'Read' function back into ProcessJointsAndAnimations() function and switching between them. Note the discrepancies between the two approaches, though. The top is your 'Read' function, the bottom is TLang1991


void FBXtoAbj::ProcessJointsAndAnimations(FbxNode *inNode)
{
    FbxMesh *currMesh = inNode->GetMesh();

    uint numDeformers = currMesh->GetDeformerCount();
    for (uint i = 0; i < numDeformers; ++i)
    {
        FbxSkin *currSkin = reinterpret_cast<FbxSkin *>(currMesh->GetDeformer(i, FbxDeformer::eSkin));

        if (!currSkin)
            continue;

        uint numClusters = currSkin->GetClusterCount();

        for (uint j = 0; j < numClusters; ++j)
        {
            FbxCluster *currCluster = currSkin->GetCluster(j);
            string currJointName = currCluster->GetLink()->GetName();
            uint currJointIndex = FindJointIndexUsingName(currJointName);

            FbxAMatrix transformMatrix, transformLinkMatrix;
            FbxAMatrix geometryTransform = GetGeometryTransformation(inNode);
            //geometryTransform.SetIdentity();

            currCluster->GetTransformMatrix(transformMatrix); //the xform of the mesh at binding time
            currCluster->GetTransformLinkMatrix(transformLinkMatrix); // the xform of the cluster(jnt) at binding time from joint space to world space
            mJoints[currJointIndex].mGlobalBindpose = transformLinkMatrix * transformMatrix * geometryTransform;
            mJoints[currJointIndex].mGlobalBindposeInverse = transformLinkMatrix.Inverse() * transformMatrix * geometryTransform;

            //mJoints[currJointIndex].mNode = currCluster->GetLink();

            //associate each joint with the ctrl pts it affects
            uint numIndices = currCluster->GetControlPointIndicesCount();

            for (uint k = 0; k < numIndices; ++k)
            {
                BlendingIndexWeightPair currBlendingIndexWeightPair;
                currBlendingIndexWeightPair.mBlendingIndex = currJointIndex;
                currBlendingIndexWeightPair.mBlendingWeight = currCluster->GetControlPointWeights()[k];

                mControlPoints[currCluster->GetControlPointIndices()[k]]->mBlendingInfo.push_back(currBlendingIndexWeightPair);
            }

            /* get animation info (for 1 "take") */
            //for (uint i = 0; i < mJoints.size(); ++i)
            {
                FbxTime::SetGlobalTimeMode(FbxTime::eFrames24);
                FbxAnimStack *currAnimStack = mScene->GetSrcObject<FbxAnimStack>(0);
                mScene->SetCurrentAnimationStack(currAnimStack);
                FbxString Name = currAnimStack->GetNameOnly();
                FbxString TakeName = currAnimStack->GetName();
                FbxTakeInfo* TakeInfo = mScene->GetTakeInfo(TakeName);
                FbxTimeSpan LocalTimeSpan = TakeInfo->mLocalTimeSpan;
                FbxTime Start = LocalTimeSpan.GetStart();
                FbxTime Stop = LocalTimeSpan.GetStop();
                FbxTime Duration = LocalTimeSpan.GetDuration();

                FbxTime::EMode TimeMode = FbxTime::GetGlobalTimeMode();
                FbxLongLong FrameCount = Duration.GetFrameCount(TimeMode);
                double FrameRate = FbxTime::GetFrameRate(TimeMode);

                for (FbxLongLong f = Start.GetFrameCount(TimeMode); f <= Stop.GetFrameCount(TimeMode); ++f)
                {
                    FbxTime Time;
                    Time.SetFrame(f, TimeMode);

                    bool useDirk = 0;
                    //bool useDirk = 1;


                    if (useDirk)
                    {
                        for (FbxNode *Node : allJointsOnFBX)
                        {
                            //if (Node->GetName() == mJoints[i].mName)
                            if (Node->GetName() == mJoints[currJointIndex].mName)
                            {
                                FbxAMatrix LocalTransform = Node->EvaluateGlobalTransform(Time);

                                //is there a parent bone? If so, need to apply that parents global inverse transform to this node's transform

                                if (FbxNode *Parent = Node->GetParent())
                                {
                                    FbxNodeAttribute *ParentAttribute = Parent->GetNodeAttribute();

                                    if (ParentAttribute && ParentAttribute->GetAttributeType() == FbxNodeAttribute::eSkeleton)
                                    {
                                        FbxAMatrix GlobalParentTransform = Parent->EvaluateGlobalTransform(Time);
                                        LocalTransform = GlobalParentTransform.Inverse() * LocalTransform;
                                    }
                                }

                                mJoints[currJointIndex].keyframes.push_back(LocalTransform);

                            }
                        }
                    }

                    else
                    {
                        FbxAMatrix currentTransformOffset = inNode->EvaluateGlobalTransform(Time) * geometryTransform;
                        FbxAMatrix LocalTransform = currentTransformOffset.Inverse() * currCluster->GetLink()->EvaluateGlobalTransform(Time);

                        mJoints[currJointIndex].keyframes.push_back(LocalTransform);
                    }

                }
            }

        }
    }

 

 

This topic is closed to new replies.

Advertisement