AREA forums upgrade
Read more about the planned upgrade of our forums
  • 1/3
You are here: Forum Home / Autodesk® FBX® / FBX SDK / Convert system unit of animation curves
IMPORTANT ANNOUNCEMENT ABOUT AREA FORUMS
  RSS 2.0 ATOM  

Convert system unit of animation curves
Rate this thread
 
64553
 
Permlink of this thread  
avatar
  • HerrBean
  • Posted: 17 February 2012 04:18 AM
  • Total Posts: 15
  • Joined: 23 November 2011 08:46 PM

Hi!

I have some problem understanding the conversion process.
I read in the FBX SDK that KFbxSystemUnit::ConvertScene() only changes the node transforms and animations (FBX help).

The problem is that when I convert my scene into meters, my animations become really huge, as if the centimeters in the animation weren’t converted, so 1cm becomes 1m…

The only solution I found was to call KFbxAnimCurve::KeyScaleValueAndTangent(), with the inverted system unit’s scale factor (0.01f) for parameter, on all the translation curves.

I’m sure there’s a better way, but I wasn’t able to find it…

Thanks,
HerrBean



Replies: 1
/userdata/avatar/vx3501hqr_small.jpg

Could you show us your code to do the conversion and attach the FBX file you are trying to convert as a zip here?

Author: Jiayang Xu

Replied: 19 February 2012 01:26 PM  
avatar
  • HerrBean
  • Posted: 21 February 2012 01:46 AM

I can’t post any data here, but I can copy part of the code. This is the workaround I found

// The first function I call after getting the scene.
    
void FileReader::convertScene(KFbxScene _pFbxScene)
    
{
        
// Convert in Max axis system and in metric system.
        
KFbxAxisSystem oldAxisSys _pFbxScene->GetGlobalSettings().GetAxisSystem()
        KFbxAxisSystem
::Max.ConvertScene(_pFbxScene);
        
KFbxSystemUnit::m.ConvertScene(_pFbxScene);

        
// All the nodes linked to the root with the name "Fbx_Root" have a special treatment.
        // It allows our animators to pack the assets together in the FBX hierarchical tree view.
        
KFbxNode root _pFbxScene->GetRootNode();
        
int numChildren root->GetChildCount();
        for(
int i 0numChildren; ++i)
        
{
            KFbxNode 
child root->GetChild(i);

            
// Get node name without prefix.
            
char const * nodeName getNameWithoutNamespace(child->GetName());

            
// Convert all the node named "Fbx_Root" (eventually with a prefix).
            
if(strcmp(nodeName"Fbx_Root") == 0)
                
KFbxAxisSystem::Max.ConvertChildren(childoldAxisSys);
        
}
    }

    
// One of the functions I call during the import.
    // It allows to parse the curves on a node.
    // FBX_E_CT_Translation{X,Y,Z} are simple integer defines.
    
void parseObjectCurves(KFbxNode _fbxNodeKFbxAnimLayer _layerKFbxAnimCurveMap _curves)
    
{
        KFbxAnimCurve 
curve NULL;

        
// Translation curves.
        
float const scaleFactor static_cast<float>(1.0f _fbxNode->GetScene()->GetGlobalSettings().GetSystemUnit().GetScaleFactor());
        
curve _fbxNode->LclTranslation.GetCurve<KFbxAnimCurve>(_layerKFCURVENODE_T_X);
        if(
curve && curve->KeyGetCount() > 0)
        
{
            curve
->KeyScaleValueAndTangent(scaleFactor);
            
_curves.insert(KFbxAnimCurveMap::value_type(FBX_E_CT_TranslationXcurve));
        
}

        curve 
_fbxNode->LclTranslation.GetCurve<KFbxAnimCurve>(_layerKFCURVENODE_T_Y);
        if(
curve && curve->KeyGetCount() > 0)
        
{
            curve
->KeyScaleValueAndTangent(scaleFactor);
            
_curves.insert(KFbxAnimCurveMap::value_type(FBX_E_CT_TranslationYcurve));
        
}

        curve 
_fbxNode->LclTranslation.GetCurve<KFbxAnimCurve>(_layerKFCURVENODE_T_Z);
        if(
curve && curve->KeyGetCount() > 0)
        
{
            curve
->KeyScaleValueAndTangent(scaleFactor);
            
_curves.insert(KFbxAnimCurveMap::value_type(FBX_E_CT_TranslationZcurve));
        
}

        
// Rotation curves.
        // Same without the KeyScaleValueAndTangent() call ...

        // Scale curves.
        // Same without the KeyScaleValueAndTangent() call ...
    
}


Replies: 0
avatar
  • HerrBean
  • Posted: 22 February 2012 04:09 AM

Why all of this convert system is so complicated ????
I still haven’t solved my problem after two days although it should be fixed in 10 minutes with a simpler SDK…

I simply want to convert my scene’s meshes and animations into meters. But with the one hundred matrices applied before having the local matrix, plus the geometric transformation, plus the pivot set, plus the EvaluateLocalTransform() that must be preferred to other ways to getting a matrix, plus the weirdly-done conversion that modify the matrices instead of the translations and the vertices, it becomes worse than a nightmare to manipulate anything!!



Replies: 0
avatar

Hello HerrBean,
As its name implies, ConverScene will take care of all the unit conversion of the whole scene, so the translation animation is also taken care by this call.
If it does not, then there might be a bug.
However, I did a quick test but cannot reproduce the issue you are reporting.
Please check the attached files, CM.fbx is the initial fbx file which has cm as unit, and has a cube translate along axis X from 0cm to 15cm. Then call the following code to convert it to CM_M.fbx which has meter as unit.
InitializeSdkObjects, LoadScene and SaveScene can be found in the Common.h/.cpp of FBX samples.

KFbxSdkManagerlSdkManager NULL;
    
KFbxScenelScene NULL;
    
bool lResult;

    
// Prepare the FBX SDK.
    
InitializeSdkObjects(lSdkManagerlScene);
    
// Load the scene.
    
lResult LoadScene(lSdkManagerlScene"CM.fbx");

    if(
lResult == false)
    
{
        printf
("\n\nAn error occurred while loading the scene...");
    
}
    
else 
    
{
 KFbxSystemUnit
::m.ConvertScene(lScene);
 
SaveScene(lSdkManagerlScene"CM_M.fbx");
    
}

If you compare the CM.fbx and CM_M.fbx, you can see the KeyValueFloat in CM.fbx is 15, but in CM_M.fbx is 0.15, 15cm == 0.15m, so they match.

So I believe there is some other reason that cause the issue, could be a problem in the initial FBX file you have, could be some other operation before or after the ConverScene that bring in incorrect effect. If you cannot provide any data for us to reproduce this issue, then I am afraid you have to do double check by yourself.

Another reminder, I noticed in your code for axis conversion, you called both ConvertScene on the scene and ConvertChlidren on all children of fbx root node, that is not necessary, ConvertScene will call ConvertChildren, so you do not need to call ConvertChildren again outside of ConvertScene.



Jiayang Xu
Maya Data Platform
Autodesk

Attachment Attachment
Replies: 2
/userdata/avatar/vx3501hqr_small.jpg

By the way, this test is done by FBX SDK 2012.2.

Author: Jiayang Xu

Replied: 22 February 2012 02:25 PM  
/img/forum/light/default_avatar.png

I’ve done a little program that explains my problem.
I can’t upload it here because it is a little bit over 3.5Mo (due to FBX lib and dll).
You can download it here: http://demo.ovh.fr/en/ecf940ba34982454731506e6aeefb1ea/

I’m sure that when you save your file in another unit system it works, but if you want to parse it to include it in a game engine, that’s yet another problem.
You’ll see that the vertices haven’t changed and the transform matrix just took a scale of 0.01. I know it’s obvious and even written in the SDK help, but as long as I need to have proper assets in the good unit system, the ConvertScene() seems completely useless to me as it is. And eventually, even if a function with the perfect name could have saved me a lot of time, I’ve got to reinvent the wheel and parse all the data by myself so that the node’s translations, the vertices, the bind poses and the animation’s translations are effectively in meters, like my engine asks them to be.

I don’t want to reset the XForm of all the nodes (I think it’s what you call “bake” in one of the example of the help), because I still need to know the scales of each of them. I simply need to change the “real” data (not the transformation matrix) into the unit system I want. And to do that, I had to multiply the data myself. It wasn’t long in the end, but I lost a lot of time to discover that ConvertScene() wasn’t the function I needed.

PS: I only call ConvertChildren() on the nodes called “Fbx_Root”, this is a convention we set with the animators. Because KFbxAxisSystem::ConvertScene() only converts the children of the root node (not recursively). This is understandable in a way… If you decided that the parent’s Y axis points toward its child, you don’t want that after the conversion it becomes the Z axis.

Author: HerrBean

Replied: 22 February 2012 08:24 PM  
avatar

OK, now you make your question clearly.
You want the unit conversion to convert the vertices values, and keep transform matrix untouched.
But FBX SDK’s strategy is to convert the transform matrix, but keep vertices info untouched.
These are just two different strategies to do unit conversion.
I don’t think one is better than the other, just for different purpose, you need to choose the more proper one.

Honestly, I don’t know the real reason why FBX SDK is choosing current unit conversion strategy.
But from a historical point of view, I would say when FBX SDK was first created, one of its main purpose is to serve as a bridge between different 3D applications, namely FilmBox(Today’s MotionBuilder) and Maya, to make sure a fbx file can be transferredba back and forth between the 2 applications. Both these 2 applications are based on cm unit system, which means internally they always store vertices info in cm, no matter what the unit you set from UI. So you can see naturally it is easier to do the unit conversion by keep vertices info and only change the transform matix.

But in recent years, FBX is began to be used in game industry, for example, Unreal official support FBX now. This new purpose makes the unit conversion strategy looks not so proper any more.
But at the same time FBX is still an important tool to be bridge between different 3D applications, and this list is even longer:3ds Max, MotionBuilder, Maya, Mudbox, SoftImage, etc.
So we cannot easily say it is time to change this strategy.

A best solution might be to support both unit conversion strategy in FBX SDK, that will make more game developer’s life easier.
But to make that decision is really beyond my power :).

Two small points to mention:
1. I believe Unreal encounter the same issue as you for the unit conversion, they also need to reinvent the wheel:), but honestly,I don’t think this is something very hard and you actually already achieved it :).
2. ConvertScene do change the translation value and translation animation curve keys value as you want, not just do that conversion when write out file.
But call ConvertScene will also change scales, which may bring more trouble for you, so it might still be better to have your own version of ConvertScene.



Jiayang Xu
Maya Data Platform
Autodesk

Replies: 0
avatar
  • HerrBean
  • Posted: 23 February 2012 12:24 AM

Thank you for your reply!

I understand that their are many historical reasons why FBX is not as perfect as it could be, it’s the same for our engine after all :)

For those who will fall on this thread here is the code I used to convert my scene:

void FileReader::convertAxisSystem(KFbxScene _fbxSceneKFbxAxisSystem const & _axisSys)
{
    
// Get root node.
    
KFbxNode root _fbxScene->GetRootNode();

    
// Convert to Max axis.
    
KFbxAxisSystem oldAxisSys _fbxScene->GetGlobalSettings().GetAxisSystem();
    
_axisSys.ConvertScene(_fbxScene);

    
// All the nodes linked to the root with the name "Fbx_Root" have a special treatment.
    // It allows the animators to pack the assets together in the FBX hierarchical tree view.
    
int numChildren root->GetChildCount();
    for(
int i 0numChildren; ++i)
    
{
        KFbxNode 
child root->GetChild(i);

        
// Get node name without prefix.
        
char const * nodeName getNameWithoutNamespace(child->GetName());

        
// Convert all the node named "Fbx_Root" (eventually with a prefix).
        
if(strcmp(nodeName"Fbx_Root") == 0)
            
_axisSys.ConvertChildren(childoldAxisSys);
    
}
}

void FileReader
::convertUnitSystem(KFbxScene _fbxSceneKFbxSystemUnit const & _unitSys)
{
    
// Get scale factor.
    
double const scaleFactor _fbxScene->GetGlobalSettings().GetSystemUnit().GetConversionFactorTo(_unitSys);
    if(
scaleFactor == 1.0f)
        return;

    
// Get root node.
    
KFbxNode root _fbxScene->GetRootNode();

    
// Parse all the nodes to convert the translations and meshes vertices.
    
std::deque<KFbxNode *> nodes;
    
nodes.push_back(root);
    while(!
nodes.empty())
    
{
        KFbxNode 
node nodes.front();
        
nodes.pop_front();

#ifdef DEBUG
        
char const * name node->GetName();
#endif // DEBUG

        // Convert node's translation.
        
fbxDouble3 lcltra node->LclTranslation.Get();
        
lcltra[0] *= scaleFactor;
        
lcltra[1] *= scaleFactor;
        
lcltra[2] *= scaleFactor;
        
node->LclTranslation.Set(lcltra);

        
KFbxMesh mesh node->GetMesh();
        if(
mesh)
        
{
            
// Convert vertices.
            
unsigned int const numVertices mesh->GetControlPointsCount();
            if(
numVertices)
            
{
                KFbxVector4 
vertexBuffer mesh->GetControlPoints();
                for(
unsigned int i 0numVertices; ++i)
                    
vertexBuffer[i] *= scaleFactor;
            
}
        }

        int 
const numChildren node->GetChildCount();
        for(
int i 0numChildren; ++i)
            
nodes.push_back(node->GetChild(i));
    
}

    
// Parse all the stacks' layers to convert the translation curves.
    
int numAnimStacks _fbxScene->GetSrcObjectCount(FBX_TYPE(KFbxAnimStack));
    for(
int i 0numAnimStacks; ++i)
    
{
        KFbxAnimStack 
stack KFbxCast<KFbxAnimStack>(_fbxScene->GetSrcObject(FBX_TYPE(KFbxAnimStack), i));

        
int nbAnimLayers stack->GetMemberCount(FBX_TYPE(KFbxAnimLayer));
        for(
int j 0nbAnimLayers; ++j)
        
{
            KFbxAnimLayer 
layer stack->GetMember(FBX_TYPE(KFbxAnimLayer), j);

            
nodes.push_back(root);
            while(!
nodes.empty())
            
{
                KFbxNode 
node nodes.front();
                
nodes.pop_front();

#ifdef DEBUG
                
char const * name node->GetName();
#endif // DEBUG

                // Convert translations curves.
                
KFbxAnimCurve curveX node->LclTranslation.GetCurve<KFbxAnimCurve>(layerKFCURVENODE_T_X);
                
KFbxAnimCurve curveY node->LclTranslation.GetCurve<KFbxAnimCurve>(layerKFCURVENODE_T_Y);
                
KFbxAnimCurve curveZ node->LclTranslation.GetCurve<KFbxAnimCurve>(layerKFCURVENODE_T_Z);
                if(
curveX)
                    
curveX->KeyScaleValueAndTangent(static_cast<float>(scaleFactor));
                if(
curveY)
                    
curveY->KeyScaleValueAndTangent(static_cast<float>(scaleFactor));
                if(
curveZ)
                    
curveZ->KeyScaleValueAndTangent(static_cast<float>(scaleFactor));

                
int const numChildren node->GetChildCount();
                for(
int i 0numChildren; ++i)
                    
nodes.push_back(node->GetChild(i));
            
}
        }
    }
}

If you find a bug, please notice me by this thread.
Thanks

Edit> This code doesn’t fix the bind poses. I had to do a special code for those, during the skeleton import pass.



Replies: 1
/userdata/avatar/vx3501hqr_small.jpg

Really thank you for sharing :).

Author: Jiayang Xu

Replied: 23 February 2012 12:58 PM