Advertisement

Building bone matrices: Same works with FBX SDK not with my own file format

Started by July 17, 2022 02:24 AM
5 comments, last by isu diss 2 years, 3 months ago

Hi all,

I have a problem when it comes to the skeletal animations.

This works with FBX SDK.

	void FBXUniLoader::BuildMatrices(FbxTime& fbxFrameTime)
{
   for (unsigned long i = 0; i < mSkeleton.mJoints.size(); ++i)
      GetNodeLocalTransform(mSkeleton.mJoints[i].mNode, fbxFrameTime, mSkeleton.mJoints[i].NodeLocalTransform);
   
   for (auto& bone : mSkeleton.mJoints)
   {
      if (bone.mParentIndex != -1)
         bone.CombinedTransform = bone.NodeLocalTransform * mSkeleton.mJoints[bone.mParentIndex].CombinedTransform;
      else
         bone.CombinedTransform = bone.NodeLocalTransform;
   }

   for (auto& bone : mSkeleton.mJoints)
      bone.BoneMatrix = bone.Offset * bone.CombinedTransform;
}

But when I exported those data to my own file format, it didn't work. I can't seem to understand why? Can anyone help me? problem lies with this code.

vector<XMMATRIX> RESrcLoader::BuildBoneMatrices(int inAnimID, float inTime)
{
	vector <XMMATRIX> BoneMatrices;

	inTime *= 1000.0f;

	if (!mAnimations.empty())
	{
		auto ad = mAnimations[inAnimID];

		for (auto &curjoint : ad.Skeleton)
		{
			if(inTime <= curjoint.Keyframes.front().TimePos)
			{
				XMVECTOR R = curjoint.Keyframes.front().Rotation;
				XMVECTOR T = curjoint.Keyframes.front().Translation;

				FbxVector4 vR, vT, vS;
				vR.Set(R.m128_f32[0], R.m128_f32[1], R.m128_f32[2], R.m128_f32[3]);
				vT.Set(T.m128_f32[0], T.m128_f32[1], T.m128_f32[2], T.m128_f32[3]);
				vS.Set(1, 1, 1);
				FbxAMatrix tmpMat;
				tmpMat.SetT(vT);
				tmpMat.SetR(vR);
				tmpMat.SetS(vS);

				FBXMatrixToXMMatrix(tmpMat, curjoint.LocalTransform);
			}
			else if(inTime >= curjoint.Keyframes.back().TimePos)
			{
				XMVECTOR R = curjoint.Keyframes.back().Rotation;
				XMVECTOR T = curjoint.Keyframes.back().Translation;

				FbxVector4 vR, vT, vS;
				vR.Set(R.m128_f32[0], R.m128_f32[1], R.m128_f32[2], R.m128_f32[3]);
				vT.Set(T.m128_f32[0], T.m128_f32[1], T.m128_f32[2], T.m128_f32[3]);
				vS.Set(1, 1, 1);
				FbxAMatrix tmpMat;
				tmpMat.SetT(vT);
				tmpMat.SetR(vR);
				tmpMat.SetS(vS);

				FBXMatrixToXMMatrix(tmpMat, curjoint.LocalTransform);
			}
			else
			{
				for (int i=0; i<ad.Length-1; i++)
				{
					if((inTime >= curjoint.Keyframes[i].TimePos) && (inTime <= curjoint.Keyframes[i+1].TimePos))
					{
						float lerpPercent = (inTime - curjoint.Keyframes[i].TimePos) / (curjoint.Keyframes[i+1].TimePos - curjoint.Keyframes[i].TimePos);

						XMVECTOR r0 = curjoint.Keyframes[i].Rotation;
						XMVECTOR r1 = curjoint.Keyframes[i+1].Rotation;

						XMVECTOR t0 = curjoint.Keyframes[i].Translation;
						XMVECTOR t1 = curjoint.Keyframes[i+1].Translation;

						XMVECTOR R = XMVectorLerp(r0, r1, lerpPercent);
						XMVECTOR T = XMVectorLerp(t0, t1, lerpPercent);

						FbxVector4 vR, vT, vS;
						vR.Set(R.m128_f32[0], R.m128_f32[1], R.m128_f32[2], R.m128_f32[3]);
						vT.Set(T.m128_f32[0], T.m128_f32[1], T.m128_f32[2], T.m128_f32[3]);
						vS.Set(1, 1, 1);
						FbxAMatrix tmpMat;
						tmpMat.SetT(vT);
						tmpMat.SetR(vR);
						tmpMat.SetS(vS);

						FBXMatrixToXMMatrix(tmpMat, curjoint.LocalTransform);
						break;
					}
				}
			}
		}

		for (auto &curjoint : ad.Skeleton)
		{
			if (curjoint.ParentID != -1)
				curjoint.CombinedTransform = curjoint.LocalTransform * ad.Skeleton[curjoint.ParentID].CombinedTransform;
			else
				curjoint.CombinedTransform = curjoint.LocalTransform;
		}

		for (auto curjoint : ad.Skeleton)
		{
			 XMMATRIX BoneMatrix = curjoint.GlobalBindposeInverse * curjoint.CombinedTransform;
			 BoneMatrices.push_back(BoneMatrix);
		}
	}
	return BoneMatrices;
}

isu diss said:
XMVECTOR R = XMVectorLerp(r0, r1, lerpPercent);

I guess this is lerping Euler angles for orientation?
This can cause discontinuities as angles jump across the +/-PI range from one frame to the other.
So i would convert both keys to quaternion first, and slerp or lerp the quaternions to avoid this issue, which usually only happens very rarely, but does.

The other thing i see is the code only works if parents are guaranteed to appear before their children in the array. Maybe you somehow changed order for your file format.

Probably neither is the reason for your issue, but maybe it helps.

Advertisement

thanks @joej for giving me an insight. I'll look into that.

FYI @joej This is the animation file

The files trigger memories about similar issues i always have when working with character data ; )

Seems the JointParent is always smaller than the JointID, but you could add an assert be sure and rule this out.

In such cases, i would look for clues in the visuals. (An image of your buggy result might help.)
If that's not enough, i start to add visualizations of the function itself. Running it every frame, and visualizing matrices as you calculate them. That's some work, but visual clues often help to reveal the bug.

I fixed it. When I write the global bind pose matrix to the file, I got mixed up the column and row of the fbx matrix. That was the problem. Here's the video of my program which facilitates my game https://www.youtube.com/watch?v=I9bjj29qaIA

This topic is closed to new replies.

Advertisement