Jump to content
  • Advertisement
Noxil

C++ FBX SDK - Export Animation

This topic is 367 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello,

I have been fighting for sometime now with exporting animations from Maya to my own engine using DirectX 11.
I export the fbx file with all its content in it. 

So the problem i have is that the animation dosnt seem to rotate the joints correctly?

Please see the attached screenshots.
bindpose.png = Frame 1 (first keyframe is at frame 2), the bindpose, no animation applied.
frame30.png = Frame 30, rotation applied to two joints.
engine.png = Frame 30 of animation in engine, rotation != rotation in Maya :( 

I use my custom tool to export data from the fbx file using the fbx sdk.

The following is how i extract the skeleton bind pose and inverse bind pose transforms using fbx sdk:
(Note: freeze transformation have been applied to the mesh, so i ignore the geometry transform since its just an identity)

FbxAMatrix localTransform = p_Node->EvaluateLocalTransform();
joint->m_Location = localTransform.GetT();
joint->m_Scale = localTransform.GetS();
joint->m_Rotation = localTransform.GetQ();

FbxAMatrix inverseBindTransform = p_Node->EvaluateGlobalTransform().Inverse();
joint->m_InverseLocation = inverseBindTransform.GetT();
joint->m_InverseScale = inverseBindTransform.GetS();
joint->m_InverseRotation = inverseBindTransform.GetQ();

Then i extract information about each join for all keyframes, in this example i only sample rotation:

for( int i=0; i<rotationCurve->KeyGetCount(); ++i ) {
	FbxAnimCurveKey key = rotationCurve->KeyGet( i );
	FbxTime time = key.GetTime();
	FbxAMatrix transform = p_Node->EvaluateLocalTransform( time );
	// setup frame
	JointFrame frame;
	frame.m_IsRotation = true;
	frame.m_Frame = time.GetFrameCount( m_AnimFramerate );
	frame.m_Rotation = transform.GetQ();
	joint->m_RotationKeyframes.add( frame );
}

When i look at the rotation values from Maya i see that sometimes the axis of the quaternion is 0,0,0 assuming that the axis is mData[0-2].
So i must be doing something wrong here right?

Thats the fbx part.
Then in my engine i load the resources.

This is how i setup and update the skeleton transforms:
(Note: m_Rotation is a quaternion)

void pmJoint::computeMatrices( pmJoint * p_Parent ) {
	if( m_IsDirty ) {
		pmMath::matrixCreateTransform( &m_LocalTransform, m_Location, m_Scale, m_Rotation );
	}
	if( p_Parent ) {
		if( p_Parent->m_IsDirty || m_IsDirty ) {
			m_WorldTransform = m_LocalTransform * p_Parent->m_WorldTransform;
			m_IsDirty = true;
		}
	} else if( m_IsDirty ) {
		m_WorldTransform = m_LocalTransform;
	}
	for( pmUInt32 i=0; i<m_Children.count(); ++i ) {
		m_Children[ i ]->computeMatrices( this );
	}
}

So as you can see, all bones operate in their localspace using their parents world to update their world.
Then to get the render matrices i call this function:

void pmJoint::computeRenderMatrices() {
	m_RenderTransform = m_InversBindTransform * m_WorldTransform;
	for( pmUInt32 i=0; i<m_Children.count(); ++i ) {
		m_Children[ i ]->computeRenderMatrices();
	}
}

The animation part is simply lerping and slerping.
I get the problem no matter if i interpolate or just show keyframe poses, so i left that code out.
 

Any input, idea or crazy thought is welcome.
Im at a loss here.

bindpose.png

frame30.png

engine.png

Share this post


Link to post
Share on other sites
Advertisement

I really cannot help you with you code but I can paste what I'm using, and it works like a charm so far:

For the bind pose(offset) matrix.

fCluster->GetTransformMatrix(transformMatrix);
fCluster->GetTransformLinkMatrix(transformLinkMatrix);	
FbxAMatrix const globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;


And then for the keyframes I use:
 

FbxAMatrix const tr = fbxNode->EvaluateLocalTransform(fbxKeyTime);

And then I decompose tr using the functions in FbxAMatrix (GetT, GetR, GetS)

It works with all  the pivots, geometric transfroms, and all that jazzy-stuff.

Share this post


Link to post
Share on other sites

Hey thanks for replying.

Im assuming you get your cluster from the FbxMesh right?
Do you know if there is a difference in going from the deformer instead of getting the node directly from the scene?
What im wondering is if the deformer has other functions or properties that does something that the node does not.

Also for the keyframes, do you use the child nodes from the deformer or how do you access them?
In my exporter im looping through the scene nodes, find a skeleton type node then iterate over all its children.

So what i can see right of the bat is that your are getting the bind pose from the deformer and im using the regular node.
Might be something?

I will have to do some tests when i get back home, thanks for sharing your example :) 

Share this post


Link to post
Share on other sites

 

Quote

Do you know if there is a difference in going from the deformer instead of getting the node directly from the scene?

There should be. Later in my code I obtain the node via the cluster:
 

Quote

fbxsdk::FbxCluster* const fCluster = fSkin->GetCluster(iCluster);
fbxsdk::FbxNode* const fNodeBone = fCluster->GetLink();

And later I extract the keyframes from "fNodeBone" to load the animation.


I do not use any skeletion nodes. I only use clusters to find the vertices, weights and the node that is used to represent the bone.

Edited by ongamex92

Share this post


Link to post
Share on other sites

Okey so now im using the cluster in the same maner you are regarding the inverse bind pose.
Also i now use the node from cluster->GetLink() to retreive the animation frame data.

And the result is the same :(

Rotation in frames is extracted like so:

FbxAMatrix transform = boneNode->EvaluateLocalTransform( time );
frame->m_Rotation = transform.GetQ();

So i dont use GetR but instead GetQ.
Is this a problem?

It must be something super simple. Everything looks right but its just not rotating all the way so to say..

Edit:
Also, i created a new mesh, new rig and new animation with less bones. Same problem. Are there some settings in Maya i need to be aware off maybe?

Edited by Noxil

Share this post


Link to post
Share on other sites

Could you share the model? if so I could try it in my importer and check the results?

 

Quote

So i dont use GetR but instead GetQ.

You should check if your quaternion has the same memory layout and meaning as the one in FBX SDK. I really can't remember why I'm using the euler angles. but here is how i convert the rotations form FBX euler angles to my internal quaterion types:
 

const FbxDouble3 fbxRotationEuler = tr.GetR();
const quatf rotation = quatFromFbx(FbxEuler::eOrderXYZ, fbxRotationEuler); // Hmmm. there is a function that returns the euler angles order, I do not know why I've hardcoded the eXYZ...

const quatf rotation = quatFromFbx(FbxEuler::eOrderXYZ, fbxRotationEuler);

// CAUTION: FBX SDK uses DEGREES this functon expects DEGREES!
// Converts an Euler angle rotation to quaternion.
quatf quatFromFbx(const fbxsdk::FbxEuler::EOrder rotationOrder, const fbxsdk::FbxDouble3& euler)
{
	const auto make_quat = [](int _1, int _2, int _3,  const fbxsdk::FbxDouble3& euler) {
		return
			  quatf::getAxisAngle(vec3f::getAxis(_3), Deg2Rad( (float)euler[_3] ) )
			* quatf::getAxisAngle(vec3f::getAxis(_2), Deg2Rad( (float)euler[_2] ) )
			* quatf::getAxisAngle(vec3f::getAxis(_1), Deg2Rad( (float)euler[_1] ) );
	};

	quatf result = quatf::getIdentity();

	switch(rotationOrder)
	{
		case fbxsdk::FbxEuler::eOrderXYZ : { result = make_quat(0,1,2, euler); } break;
		case fbxsdk::FbxEuler::eOrderXZY : { result = make_quat(0,2,1, euler); } break;
		case fbxsdk::FbxEuler::eOrderYZX : { result = make_quat(1,2,0, euler); } break;
		case fbxsdk::FbxEuler::eOrderYXZ : { result = make_quat(1,0,2, euler); } break;
		case fbxsdk::FbxEuler::eOrderZXY : { result = make_quat(2,0,1, euler); } break;
		case fbxsdk::FbxEuler::eOrderZYX : { result = make_quat(2,1,0, euler); } break;

		default :
		{
			throw FBXParseError("Unknown FBX rotation order!");
		}break;
	}

	return result;
}

 

Share this post


Link to post
Share on other sites

Hi, I post my code here which should be relatively simple and have another look later if you want. Basically I collect all skeleton nodes (bones) and then I read the animation from these nodes. The animation is stored as an array of poses (as opposed to an array of tracks). Another good reference is the Ozz Animation Library. In particular if you need to transform the coordinate system or units. The FBX library can mess things up since it only applies some transformation in the root. 

Quote

void RnAnimation::Read( FbxScene* Scene, FbxAnimStack* AnimStack )
    {
    // Collect nodes
    RnArray< FbxNode* > Nodes;
    Nodes.Reserve( Scene->GetNodeCount() );

    int Count = 0;
    FbxNode* Stack[ STACK_SIZE ];
    Stack[ Count++ ] = Scene->GetRootNode();

    while ( Count > 0 )
        {
        // Pop FBX source node
        FbxNode* Node = Stack[ --Count ];

        // Inspect node attributes
        FbxNodeAttribute* Attribute = Node->GetNodeAttribute();
        if ( Attribute && Attribute->GetAttributeType() == FbxNodeAttribute::eSkeleton )
            {
              Nodes.PushBack( Node );   
            }

        // Recurse
        for ( int Index = Node->GetChildCount() - 1; Index >= 0; --Index )
            {
            RN_ASSERT( Count < STACK_SIZE );
            Stack[ Count++ ] = Node->GetChild( Index );
            }
        }

    mTrackCount = Nodes.Size();
    mTrackNames.Reserve( mTrackCount );

    for ( FbxNode* Node : Nodes )
        {
        RnString NodeName = Node->GetNameOnly();
        mTrackNames.PushBack( NodeName );
        }

    // Process keyframes
    FbxString TakeName = AnimStack->GetName();
    FbxTakeInfo* TakeInfo = Scene->GetTakeInfo( TakeName );
    FbxTime::EMode TimeMode = FbxTime::GetGlobalTimeMode();
    FbxTime Start = TakeInfo->mLocalTimeSpan.GetStart(); 
    FbxLongLong StartFrame = Start.GetFrameCount( TimeMode );
    FbxTime Stop = TakeInfo->mLocalTimeSpan.GetStop();
    FbxLongLong StopFrame = Stop.GetFrameCount( TimeMode );

    FbxLongLong FrameCount = StopFrame - StartFrame;
    mFrameCount = static_cast< int >( FrameCount );
    double FrameRate = FbxTime::GetFrameRate( TimeMode );
    mFrameRate = static_cast< float >( FrameRate );
    
    int KeyCount = ( mFrameCount + 1 ) * mTrackCount;
    mTranslationKeys.Reserve( KeyCount );
    mRotationKeys.Reserve( KeyCount );
    mScaleKeys.Reserve( KeyCount );

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

        for ( FbxNode* Node : Nodes )
            {
            // Evaluate and decompose
            FbxAMatrix Transform = Node->EvaluateLocalTransform( Time );

            RnVector3 Translation( Transform.GetT() );
            mTranslationKeys.PushBack( Translation );
            RnQuaternion Rotation( Transform.GetQ() );
            mRotationKeys.PushBack( Rotation );
            RnVector3 Scale( Transform.GetS() );
            mScaleKeys.PushBack( Scale );
            }
        }
    }

 

Edited by DonDickieD

Share this post


Link to post
Share on other sites

Thank you both for your replies, i will make some tests regarding your posts.
As per request from ongamex92 i have attached the maya file and fbx file.

Edit:
Reuploaded .mlt file, it hade unsaved changes (dont know what they where)

anim_scene.fbx

 

anim_test.mlt

Edited by Noxil

Share this post


Link to post
Share on other sites

So i have tried some euler to quaternion with no success.
I get a another type of rotation, it dosnt rotate from the root bone any more but just slighly bends in the middle instead.
It looks further away from the direction i should be heading if i compare to the quaternion rotation.
But im not quite sure that i got it all right.

I did the quaternion multiplications myself, aswell as to use the DX11 function "XMQuaternionRotationRollPitchYaw".
They both gave the same result.

It feels like the quaternion i get from "GetQ" using "boneNode->EvaluateLocalTransform(time)" is not the correct value or something. 
When i load the frames and extract the quaternion in my engine. If i multiply the w component with 2.0f the animation seems to rotate almost to the point where it should.

Share this post


Link to post
Share on other sites

This is a long shot, but one thing I noticed with XNAMath/DirectXMath is that the quaternion multiplication is reversed. Say you two rotations q1 and q2. In usual quaternion notation q = q2 * q1 would apply q1 first followed by q2. If you are using XNAMath they changed that order to match the matrix multiplication So q = q1 * q2 is how you need to concatenate. E.g. see here for an explanation:

 https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.quaternion.xmquaternionmultiply(v=vs.85).aspx

So if you use the operator overload you might be in trouble. I am sure you know this already. Still I point this out since I have seen this issue before. IIRC the Maya SDK does similar things. Personally I hate this, but I can see what they are trying to do.

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!