Area :: Header
Discover the Difference with
3ds Max 2015
Upgrade now and save
Discussion Groups

FBX SDK

Reply
Contributor
HerrBean
Posts: 15
Registered: ‎11-24-2011

Convert system unit of animation curves

251 Views, 9 Replies
02-17-2012 09:18 AM
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
Please use plain text.
Distinguished Contributor
jiayang.xu
Posts: 504
Registered: ‎04-21-2008

Re: Convert system unit of animation curves

02-19-2012 06:26 PM in reply to: HerrBean
Could you show us your code to do the conversion and attach the FBX file you are trying to convert as a zip here?
Please use plain text.
Contributor
HerrBean
Posts: 15
Registered: ‎11-24-2011

Re: Convert system unit of animation curves

02-21-2012 06:46 AM in reply to: HerrBean
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 = 0; i < numChildren; ++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(child, oldAxisSys);
}
}

// 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 * _fbxNode, KFbxAnimLayer * _layer, KFbxAnimCurveMap & _curves)
{
KFbxAnimCurve * curve = NULL;

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

curve = _fbxNode->LclTranslation.GetCurve<KFbxAnimCurve>(_layer, KFCURVENODE_T_Y);
if(curve && curve->KeyGetCount() > 0)
{
curve->KeyScaleValueAndTangent(scaleFactor);
_curves.insert(KFbxAnimCurveMap::value_type(FBX_E_CT_TranslationY, curve));
}

curve = _fbxNode->LclTranslation.GetCurve<KFbxAnimCurve>(_layer, KFCURVENODE_T_Z);
if(curve && curve->KeyGetCount() > 0)
{
curve->KeyScaleValueAndTangent(scaleFactor);
_curves.insert(KFbxAnimCurveMap::value_type(FBX_E_CT_TranslationZ, curve));
}

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

// Scale curves.
// Same without the KeyScaleValueAndTangent() call ...
}
Please use plain text.
Contributor
HerrBean
Posts: 15
Registered: ‎11-24-2011

Re: Convert system unit of animation curves

02-22-2012 09:09 AM in reply to: HerrBean
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!!
Please use plain text.
Distinguished Contributor
jiayang.xu
Posts: 504
Registered: ‎04-21-2008

Re: Convert system unit of animation curves

02-22-2012 07:17 PM in reply to: HerrBean
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.
    KFbxSdkManager* lSdkManager = NULL;
KFbxScene* lScene = NULL;
bool lResult;

// Prepare the FBX SDK.
InitializeSdkObjects(lSdkManager, lScene);
// Load the scene.
lResult = LoadScene(lSdkManager, lScene, "CM.fbx");

if(lResult == false)
{
printf("\n\nAn error occurred while loading the scene...");
}
else
{
KFbxSystemUnit::m.ConvertScene(lScene);
SaveScene(lSdkManager, lScene, "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.

unitconversionissue.zip

Please use plain text.
Distinguished Contributor
jiayang.xu
Posts: 504
Registered: ‎04-21-2008

Re: Convert system unit of animation curves

02-22-2012 07:25 PM in reply to: jiayang.xu
By the way, this test is done by FBX SDK 2012.2.
Please use plain text.
Contributor
HerrBean
Posts: 15
Registered: ‎11-24-2011

Re: Convert system unit of animation curves

02-23-2012 01:24 AM in reply to: jiayang.xu
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.
Please use plain text.
Distinguished Contributor
jiayang.xu
Posts: 504
Registered: ‎04-21-2008

Re: Convert system unit of animation curves

02-23-2012 02:56 AM in reply to: HerrBean
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 :smileyhappy:.


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:smileyhappy:, but honestly,I don't think this is something very hard and you actually already achieved it :smileyhappy:.
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.
Please use plain text.
Contributor
HerrBean
Posts: 15
Registered: ‎11-24-2011

Re: Convert system unit of animation curves

02-23-2012 05:24 AM in reply to: HerrBean
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 :smileyhappy:

For those who will fall on this thread here is the code I used to convert my scene:
void FileReader::convertAxisSystem&#40;KFbxScene * _fbxScene, KFbxAxisSystem const & _axisSys&#41;
{
// Get root node.
KFbxNode * root = _fbxScene->GetRootNode();

// Convert to Max axis.
KFbxAxisSystem oldAxisSys = _fbxScene->GetGlobalSettings().GetAxisSystem&#40;&#41;;
_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 = 0; i < numChildren; ++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(child, oldAxisSys);
}
}

void FileReader::convertUnitSystem&#40;KFbxScene * _fbxScene, KFbxSystemUnit const & _unitSys&#41;
{
// 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 *= scaleFactor;
lcltra *= scaleFactor;
lcltra *= 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 = 0; i < numVertices; ++i)
vertexBuffer *= scaleFactor;
}
}

int const numChildren = node->GetChildCount();
for(int i = 0; i < numChildren; ++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 = 0; i < numAnimStacks; ++i)
{
KFbxAnimStack * stack = KFbxCast<KFbxAnimStack>(_fbxScene->GetSrcObject(FBX_TYPE(KFbxAnimStack), i));

int nbAnimLayers = stack->GetMemberCount(FBX_TYPE(KFbxAnimLayer));
for(int j = 0; j < nbAnimLayers; ++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>(layer, KFCURVENODE_T_X);
KFbxAnimCurve * curveY = node->LclTranslation.GetCurve<KFbxAnimCurve>(layer, KFCURVENODE_T_Y);
KFbxAnimCurve * curveZ = node->LclTranslation.GetCurve<KFbxAnimCurve>(layer, KFCURVENODE_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 = 0; i < numChildren; ++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.
Please use plain text.
Distinguished Contributor
jiayang.xu
Posts: 504
Registered: ‎04-21-2008

Re: Convert system unit of animation curves

02-23-2012 05:58 PM in reply to: HerrBean
Really thank you for sharing :smileyhappy:.
Please use plain text.