Followers 0

Combining multiple .x files into a single .x file

29 posts in this topic

I have created multiple .x files, each one got different animation, example: "walk.x", "run.x", "die.x".

How do I combine them together to create "character.x"?

I tried using DirectX SDK tool called MeshView, but it's not working, I don't get to see the animations in the program.

I'm using Alin's DirectX Exporter for exporting .x file from 3Ds Max.

0

Share on other sites

You don't need to do this programmatically. You can do it from the command line or using a batch script.

Assuming all your X files are in text format, just trim all but one of the X Files on disk so that they only have "AnimationSets" in them. Leave one of the X files so it has the other skinned mesh pieces (like Frames and Meshes), now just use 'copy' from the command line to concatenate all the files into one.

e.g.

copy /A bugbear.x + bugbear_walk.x + bugbear_idle.x bugbear_all.x

This leaves a file bugbear_all.x that has the primary skinned mesh as well as the walk and idle animation sets. Unfortunately the copy command leaves a redundant EOF character at the end of the file, which you will have to remove somehow.

2

Share on other sites

If it's BIP  animations, then simply combine them in the 'Mixer' in Max (select root skeletal node and click on the Motion tab). I don't know if Alin's exporter supports multiple animations in one file, but Pandasoft's exporter most certainly does. Doing it this way means you don't have to post-combine them if you use the animations on another character.

0

Share on other sites

as i suspected, and as Steve's post confirms, you can put multiple animations in a single .x file. another way to do it is to use text format, and simply cut and paste them together using a text editor. double check your .xof file format description in the directx docs to make sure you get the syntax right. .x files tend to nest stuff using lots of "{ }'s " as i recall.

since i don't use dx animation system anymore

out of curiosity, what do you use now?

0

Share on other sites

out of curiosity, what do you use now?

I am using my own mesh format and animation system, since i always wanted full control over things and i really hated dx a.s. guts.

It is working but not perfect yet (having some problems with transitions between animations... long story) but i am working really hard and i will resolve my issues.

Also i have created 3ds max utility plugin using their IGame interface, with which i was surprised how easy it was to use, to export animation (and other) data that i need.

0

Share on other sites
Okay, trying to open and save .x file and the following code fail, getting D3DERR_INVALIDCALL when I call D3DXSaveMeshHierarchyToFile():
LPD3DXANIMATIONCONTROLLER animController;
hr = D3DXLoadMeshHierarchyFromX(filePath, D3DXMESH_MANAGED, device,
memoryAllocator, NULL, &frameRoot, &animController);
assert(SUCCEEDED(hr)); // OK

hr = D3DXSaveMeshHierarchyToFile("character_with_all_animations.x", D3DXF_FILEFORMAT_TEXT, frameRoot, animController, NULL);
assert(SUCCEEDED(hr)); // <---- ERROR: Getting D3DERR_INVALIDCALL here
Edited by Medo3337
0

Share on other sites

I have just tried the same thing, saving immediately after load function and it succeeded for me.

Enable dx debug runtime to examine any error logs you might have in output window. Do you know how?

Edited by belfegor
0

Share on other sites

@belfegor: Notice that "memoryAllocator" is associated with MeshHierarchy class.

I'm running debug mode and don't see any error message in the output window.

Here is main.cpp source code:

// main.cpp
#include "windows.h"
#include "d3d9.h"
#include "d3dx9.h"
#include "MeshHierarchy.h"
#include "assert.h"

#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

LPDIRECT3DDEVICE9 device;
LPDIRECT3D9 d3d;

int main()
{
d3d = Direct3DCreate9(D3D9b_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.BackBufferWidth = 800;
d3dpp.BackBufferHeight = 600;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
d3dpp.hDeviceWindow = GetConsoleWindow();
d3dpp.BackBufferCount = 1;

HRESULT hr;
hr = d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, GetConsoleWindow(),
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&d3dpp,
&device);
assert(SUCCEEDED(hr));

CMeshHierarchy *memoryAllocator = new CMeshHierarchy;

LPD3DXFRAME frameRoot;
LPD3DXANIMATIONCONTROLLER animController;
hr = D3DXLoadMeshHierarchyFromX("character.x", D3DXMESH_MANAGED, device,
memoryAllocator, NULL, &frameRoot, &animController);
assert(SUCCEEDED(hr));

hr = D3DXSaveMeshHierarchyToFile("character_with_all_animations.x", D3DXF_FILEFORMAT_TEXT, frameRoot, animController, NULL);
if (hr == D3DERR_INVALIDCALL)
MessageBox(NULL, "Invalid D3D Call", "Error", MB_ICONERROR | MB_OK);
assert(SUCCEEDED(hr));
}
0

Share on other sites

You have not enabled DX debug runtime!

1. You need to link with debug dx lib: d3dx9d.lib (notice the d)

2. Set this at top of your cpp (before including any dx headers):

#define D3D_DEBUG_INFO          1


3. Set this options in dx control panel (dxcpl.exe), you can find it in DX SDK utilities folder:

4. Run your app with debugging and close it

5. Examine output window for DX error/warning logs

2

Share on other sites
I fixed this problem, now I have a slight another problem, I couldn't combine the animations together:
vector<LPCSTR> filePath;
filePath.push_back("walk.x");
filePath.push_back("die.x");

LPD3DXFRAME frameRoot;
LPD3DXANIMATIONCONTROLLER newController;

DWORD numAllAnimations = 0;
for(UINT i = 0; i < filePath.size(); i++)
{
LPD3DXANIMATIONCONTROLLER animController;
hr = D3DXLoadMeshHierarchyFromX(filePath[i], D3DXMESH_MANAGED, device,
memoryAllocator, NULL, &frameRoot, &animController);
assert(SUCCEEDED(hr));

numAllAnimations += animController->GetNumAnimationSets();

hr = animController->CloneAnimationController(animController->GetMaxNumAnimationOutputs(),
numAllAnimations,
animController->GetMaxNumTracks(),
animController->GetMaxNumEvents(),
&newController);
assert(SUCCEEDED(hr));

for(UINT j = 0; j < animController->GetNumAnimationSets(); j++)
{
LPD3DXANIMATIONSET set;
animController->GetAnimationSet(j, &set);
newController->RegisterAnimationSet(set);
}
}

delete memoryAllocator;
memoryAllocator=0;

hr = D3DXSaveMeshHierarchyToFile("G:\\character_with_all_animations.x", D3DXF_FILEFORMAT_TEXT, frameRoot, newController, NULL);
if (hr == D3DERR_INVALIDCALL)
MessageBox(NULL, "Invalid D3D Call", NULL, MB_ICONERROR | MB_OK);
assert(SUCCEEDED(hr));

I can only get one of the animations to work, not both.

0

Share on other sites

You have to put CloneAnimationController before loop so it is cloned only once. To make it easy for me to explain with little code as possible i am gonna assume you have only one animation set in each x file, then you could improve it later for multiple animations per x file when you got this working:

vector<LPCSTR> filePath;
filePath.push_back("walk.x");
filePath.push_back("die.x");
filePath.push_back("idle.x");
filePath.push_back("crouch.x");
filePath.push_back("wave.x");
filePath.push_back("jump.x");

DWORD numAllAnimations = filePath.size();

LPD3DXFRAME frameRoot, othersFrameRoot;
LPD3DXANIMATIONCONTROLLER newController;
LPD3DXANIMATIONCONTROLLER animController;

memoryAllocator, NULL, &frameRoot, &animController);

animController->CloneAnimationController(animController->GetMaxNumAnimationOutputs(),
numAllAnimations,
animController->GetMaxNumTracks(),
animController->GetMaxNumEvents(),
&newController);

for(UINT i = 1; i < filePath.size(); i++) // note that we skip first
{

memoryAllocator, NULL, &othersFrameRoot, &animController);

LPD3DXANIMATIONSET set;
animController->GetAnimationSet(0, &set);
newController->RegisterAnimationSet(set);
}

delete memoryAllocator;
memoryAllocator=0;

hr = D3DXSaveMeshHierarchyToFile("G:\\character_with_all_animations.x", D3DXF_FILEFORMAT_TEXT, frameRoot, newController, NULL);


I am not sure if CloneAnimationCotroller will copy animation set in new controller from one that with cloned from, since MSDN docs are incomplete and obscure, so you need to test that.

As always check all functions for failure, i removed it for readability. Also there is some memory leaks here but it doesn't matter much you can fix that later until you got this working first.

Edited by belfegor
1

Share on other sites

@belfegor: Working now! some animations like "die" should not loop, how do I stop it from looping? I want to play "some" animations just once.

"walk", "run" animations working correctly since they should loop, however "die" is looping while it should not.

0

Share on other sites

Is it possible that I determine that the animation should play once or loop from 3Ds Max?

I never use D3DXCreateKeyframedAnimationSet() in my code, instead, I'm getting the existing animation set using GetAnimationSet() and assigning it to the track.

0

Share on other sites

Is it possible that I determine that the animation should play once or loop from 3Ds Max?

It doesn't work like that.

Just stop updating your bone matrices when "die" animation finishes, this way you also save yourself a dozen matrix multiplies and what not.

Edited by belfegor
0

Share on other sites

Okay, so how do I know that the animation finished so I can stop updating the bone matrices?

Or maybe there is a way to set D3DXPLAY_ONCE, how do I update the following code to set D3DXPLAY_ONCE?

UINT animationIndex = 0; // Animation that I want to play
LPD3DXANIMATIONSET set;
animController->GetAnimationSet(animationIndex, &set);
0

Share on other sites

Okay, so how do I know that the animation finished so I can stop updating the bone matrices?

Or maybe there is a way to set D3DXPLAY_ONCE, how do I update the following code to set D3DXPLAY_ONCE?

This is why i hate ("hate" is even light word for what i think about) dx animation system as there is no convenient way to do this, but instead you need to do stupid hoops and hacks to setup such a simple thing.

From what i gathered from Frank Luna animation book you need to set a callback and create compressed keyframed animation set, should look something like this:

...
ID3DXKeyframedAnimationSet* animSetTemp = 0;  _animCtrl->GetAnimationSet(
dieAnimationSetInx, (ID3DXAnimationSet**)&animSetTemp);
// Compress it.
ID3DXBuffer* compressedInfo = 0;
animSetTemp->Compress(D3DXCOMPRESS_DEFAULT,
0.5f, 0, &compressedInfo);

UINT numCallbacks = 1;
D3DXKEY_CALLBACK key;

double ticks = animSetTemp->GetSourceTicksPerSecond();
keys.Time = animSetTemp->GetPeriod()*ticks;
keys.pCallbackData = (void*)&MyCallbackData;

ID3DXCompressedAnimationSet* compressedAnimSet = 0;
D3DXCreateCompressedAnimationSet(animSetTemp->GetName(),
animSetTemp->GetSourceTicksPerSecond(),
animSetTemp->GetPlaybackType(), compressedInfo,
numCallbacks, &key, &compressedAnimSet);

compressedInfo->Release();
// Remove the old (non compressed) animation set.
_animCtrl->UnregisterAnimationSet(animSetTemp);
animSetTemp->Release();
// Add the new (compressed) animation set.
_animCtrl->RegisterAnimationSet(compressedAnimSet);
// Hook up the animation set to the first track.
_animCtrl->SetTrackAnimationSet(0, compressedAnimSet);
compressedAnimSet->Release();

0

Share on other sites

Hmm, the code is throwing "Access violation" exception, but the purpose in the code is to set a callback for the animations.

Why can't I simply set the flag D3DXPLAY_ONCE? I believe it can be done by simply setting D3DXPLAY_ONCE somewhere instead of doing callbacks.

0

Share on other sites

Here is what I tried to do to set D3DXPLAY_ONCE flag (not working though):

ID3DXKeyframedAnimationSet* set = 0;
animController->GetAnimationSet(animationIndex, (ID3DXAnimationSet**)&set);
D3DXCreateKeyframedAnimationSet(set->GetName(), set->GetSourceTicksPerSecond(), D3DXPLAY_ONCE, set->GetNumAnimations(), set->GetNumCallbackKeys(), NULL, &set);

// Code here to set the track and play the animation...
0

Share on other sites

From what i can tell D3DXCreateKeyframedAnimationSet only outputs ID3DXKeyframedAnimationSet it does not copy SRT keys from existing animation set, you need to that yourself.
Note that i have not done this before so i am going to guess with help from MSDN documentation:

ID3DXAnimationSet* dieAnimationSet;
ID3DXKeyframedAnimationSet* dieKeyFramedAnimationSet;

animController->GetAnimationSet(animationIndex, &dieAnimationSet);

// creates "empty" keyframed-animation-set
D3DXCreateKeyframedAnimationSet(
dieAnimationSet->GetName(),
dieAnimationSet->GetSourceTicksPerSecond(),
D3DXPLAY_ONCE,
dieAnimationSet->GetNumAnimations(),
dieAnimationSet->GetNumCallbackKeys(),
NULL,
&dieKeyFramedAnimationSet);

// now you need to copy SRT keys data from dieAnimationSet to dieKeyFramedAnimationSet
// some thing here i am not sure how to obtain as documentation is not clear enough
LPD3DXKEY_VECTOR3 scaleKeys[ numKeys ];
LPD3DXKEY_QUATERNION rotKeys[ numKeys ];
LPD3DXKEY_VECTOR3 translKeys[ numKeys ];

for each numKeys and "periodic/local/whatever_time_they_mean_by_this"
{
D3DXVECTOR3 scale, transl;
D3DXQUATERNION rot;
dieAnimationSet->GetSRT( timePos, 0, &scale, &rot, &transl );
scaleKeys[ i ].Time = timePos;
scaleKeys[ i ].Value = scale;
rotKeys[ i ].Time = timePos;
rotKeys[ i ].Value = rot;
translKeys[ i ].Time = timePos;
translKeys[ i ].Value = transl;
}

DWORD newAnimIndex;
dieKeyFramedAnimationSet->RegisterAnimationSRTKeys(
"animation_name",
numKeys, numKeys, numKeys,
scaleKeys, rotKeys, translKeys, &newAnimIndex);

// then here unregister old dieAnimationSet and register new dieKeyFramedAnimationSet to animation controler


Now look how stupid this is for setting such a small and trivial thing.

Edited by belfegor
0

Share on other sites

@belfegor: I'm new to D3DX animation, so I will need a little more help

and "periodic/local/whatever_time_they_mean_by_this"

I don't understand this part

How do I get numKeys?

The following functions doesn't exists:

dieAnimationSet->GetSourceTicksPerSecond()
dieAnimationSet->GetNumCallbackKeys()
0

Share on other sites

I am sorry, i just copied your code from above for that function and didn't look if it is correct.

I commented in code that i don't know for some things how to obtain them.

You could put it like this for test:

D3DXCreateKeyframedAnimationSet(
dieAnimationSet->GetName(),
30.0, // for this check in 3ds max and/or exporter options on which value it was set
D3DXPLAY_ONCE,
1, // assume 1 animation in set
0, // assume no callback keys, otherwise i don't know how to obtain them
NULL,
&dieKeyFramedAnimationSet);


Then for numKeys you could try (my guess is that GetPeriod() is in seconds?):

double timeStep = dieAnimationSet->GetPeriod() / 30.0;
DWORD numKeys = std::floor(dieAnimationSet->GetPeriod() * 30);

double timePos = 0.0;
for(DWORD i =0; i < numKeys; ++i, timePos += timeStep)
{
D3DXVECTOR3 scale, transl;
D3DXQUATERNION rot;
dieAnimationSet->GetSRT( timePos, 0, &scale, &rot, &transl );
...
}


Maybie there is an easier way but i don't see one.

Edited by belfegor
0

Share on other sites

Now, the animation is not playing anymore :/

Here is the code:

LPD3DXANIMATIONSET tempSet = 0;
LPD3DXKEYFRAMEDANIMATIONSET set;
m_animController->GetAnimationSet(index, &tempSet);

D3DXCreateKeyframedAnimationSet(
tempSet->GetName(),
30.0, // for this check in 3ds max and/or exporter options on which value it was set
D3DXPLAY_ONCE,
1, // assume 1 animation in set
0, // assume no callback keys, otherwise i don't know how to obtain them
NULL,
&set);

double timeStep = tempSet->GetPeriod() / 30.0;
DWORD numKeys = std::floor(tempSet->GetPeriod() * 30);

// now you need to copy SRT keys data from dieAnimationSet to dieKeyFramedAnimationSet
// some thing here i am not sure how to obtain as documentation is not clear enough
LPD3DXKEY_VECTOR3 scaleKeys = new D3DXKEY_VECTOR3[numKeys]();
LPD3DXKEY_QUATERNION rotKeys = new D3DXKEY_QUATERNION[numKeys]();
LPD3DXKEY_VECTOR3 translKeys = new D3DXKEY_VECTOR3[numKeys]();

double timePos = 0.0;
for(DWORD i =0; i < numKeys; ++i, timePos += timeStep)
{
D3DXVECTOR3 scale, transl;
D3DXQUATERNION rot;
tempSet->GetSRT( timePos, 0, &scale, &rot, &transl );

scaleKeys[ i ].Time = timePos;
scaleKeys[ i ].Value = scale;
rotKeys[ i ].Time = timePos;
rotKeys[ i ].Value = rot;
translKeys[ i ].Time = timePos;
translKeys[ i ].Value = transl;
}

DWORD newAnimIndex;
set->RegisterAnimationSRTKeys(
tempSet->GetName(),
numKeys, numKeys, numKeys,
scaleKeys, rotKeys, translKeys, &newAnimIndex);

animController->UnregisterAnimationSet(tempSet);
tempSet->Release();
animController->RegisterAnimationSet(set);

0

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account