Smooth animation and frame transition

Started by
10 comments, last by Bombshell93 12 years ago
EDIT: previously "Assimp Skinned Meshes" I changed the name to go with the new subject, I figured opening another topic would be a waste
[size=1]I'm writing the function to process the aiScene into the Model object,
My Model Layout is basicly:
Current Transforms + Model -> ModelParts . Bones + Animations + Material + Mesh . MeshParts . VertexBuffer + IndexBuffer
The Vertices contain position, uv, normal, bitangent, tanget, bone weights and bone index.
bone weights are 3 floats and a forth will be found by assuming they all add up to 1.
Animations hold their name and frames consisting of a time frame, bones and their index (so for example leg only animations can be kept)
And bones contain only their transform and a pointer to their parent and children.

The vertex position, uv, normal, bitangent and tangent are all kept together easy peasy. But I don't know how the per vertex bone weights and index are found.
Similarly the animations confuse me, scattered in pieces all over the places!

Any and all help would be greatly appreciated,
Thanks in advanced,
Bombshell


I did some fiddling around and I've got animations and mesh data loading fine (so far as I can tell)
problem is now I have the animations keys, I'm not sure how I should interpolate them. I could have it just linear interpolate but I'd imagine that'd lead to rather stiff animations?
what would I do alternatively?

atm my Node (bone) class is held independently per animation,
it has its index, used for identification in the shader with the vertices.
it has position keys (in a map, time as key, position as value)
rotation quaternion keys (map, time as key, rotation as value)
scaling keys (map, time as key, scaling as value)

with the time as the key, its simple enough to pass the current animation relative time and get the upper and lower bounds of that value. from there linear interpolation is fairly straight forward,
InterpolationValue = (CurrentTime - LowerTime) / (Uppertime - LowerTime); //which should give a value between 0 and 1 to use as the interpolation value.
but how would I have this smoothly transition into other frames or other animations? could I keep 2 Previous matrices and estimate some kind of curved interpolation?

EDIT: I've found my way around smooth animation via http://www.gamedev.n...aced-keyf-r1497
But I have a new problem I'm getting the following error
Direct3D9: (ERROR) :ps_3_0 shader can only be used with vs_3_0+ shader (SWVP or HWVP), or transformed vertices.
With the HResult returning "The pipe state is invalid"
Here is my .fx file at the moment.



float4x4 World : WORLDMATRIX;
float4x4 View : VIEWMATRIX;
float4x4 Proj : PROJMATRIX;

float4x4 Transforms[64] : BONES;

struct VSI
{
float4 Position : POSITION;
float3 Weights : BLENDWEIGHT;
float4 Indices : BLENDINDICES;
float2 UV : TEXCOORD;
};

struct VSO
{
float4 Position : SV_POSITION;
float2 UV : TEXCOORD;
};

VSO VS(VSI input)
{
VSO output;
float4 BlendPosition = mul(input.Position, Transforms[input.Indices.x]) * input.Weights.x;
BlendPosition += mul(input.Position, Transforms[input.Indices.y]) * input.Weights.y;
BlendPosition += mul(input.Position, Transforms[input.Indices.z]) * input.Weights.z;
BlendPosition += mul(input.Position, Transforms[input.Indices.w]) * (1 - input.Weights.x - input.Weights.y - input.Weights.z);
float4 Position = mul(BlendPosition, World);
Position = mul(Position, View);
output.Position = mul(Position, Proj);
output.UV = input.UV;
return output;
}

float4 PS(VSO input) : SV_TARGET
{
return float4(input.UV, 1, 1);
}

technique Default
{
pass p0
{
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
}
}

Once again any and all help appreciated,
Thanks in advanced,
Bombshell
Advertisement
Okay so I've found out how to fill in the vertex weight and index, but first I need to get hold of all bones and give each of them an identifying number (which can then be used for the bone indices)
But to do that I need to go through every mesh (in this case representing mesh parts) and every bone under those meshes. I don't intend to use assimp for the runtime so I suppose some speed loss on initial conversion shouldn't be much of a deal.

But I'm still stuck with the animations, all I want is to take the time length and the transform matrix for each bone (relevant to the bone it transforms, this is important so skeletal logic can take place as well as animation) on the keyframes.
I've given it a shot but I'm unsure if this will work (and I have quite a bit more to add in before its testable) so could someone tell me if this looks fairly solid or not please?
Ignore the pointers I've not destructed, I will sort them out when it comes to it, for now just assume they all magically sort themselves and concentrate on the logic rather than the memory.
The following does compile but as I said, I've still got quite a bit to handle before its testable:

void ::ModelClass::ProcessMesh(const aiScene* scene)
{
ModelPartClass* _modelPart = new ModelPartClass[1];
ModelMeshClass* _modelMesh = new ModelMeshClass();
ModelMeshPart* _meshParts = new ModelMeshPart[scene->mNumMeshes];
AnimationLibrary* _animationLibrary = new AnimationLibrary();
AnimationClass* _animations = new AnimationClass[scene->mNumAnimations];
FrameClass* _defaultFrame = new FrameClass();
VertexTypes::Vertex_UV_WEIGHT_NORM_TANGENT* _vertices;
unsigned long* _indices;
vector<string> _boneIdentities = vector<string>();
vector<unsigned short> _boneIndices = vector<unsigned short>();
unsigned short _nboneCount = 0;
aiMesh* _currentMesh;
for (unsigned int i = 0; i < scene->mNumMeshes; i++)
{
_currentMesh = scene->mMeshes;
_vertices = new VertexTypes::Vertex_UV_WEIGHT_NORM_TANGENT[_currentMesh->mNumVertices];
_indices = new unsigned long[_currentMesh->mNumFaces * 3];
for (unsigned long j = 0; j < _currentMesh->mNumVertices; j++)
{
_vertices[j].Position.x = _currentMesh->mVertices[j].x;
_vertices[j].Position.y = _currentMesh->mVertices[j].y;
_vertices[j].Position.z = _currentMesh->mVertices[j].z;

_vertices[j].Normal.x = _currentMesh->mNormals[j].x;
_vertices[j].Normal.y = _currentMesh->mNormals[j].y;
_vertices[j].Normal.z = _currentMesh->mNormals[j].z;

_vertices[j].Tangent.x = _currentMesh->mTangents[j].x;
_vertices[j].Tangent.y = _currentMesh->mTangents[j].y;
_vertices[j].Tangent.z = _currentMesh->mTangents[j].z;

_vertices[j].BiNormal.x = _currentMesh->mBitangents[j].x;
_vertices[j].BiNormal.y = _currentMesh->mBitangents[j].y;
_vertices[j].BiNormal.z = _currentMesh->mBitangents[j].z;

_vertices[j].UV.x = _currentMesh->mTextureCoords[j][0].x;
_vertices[j].UV.y = _currentMesh->mTextureCoords[j][0].y;
}
aiBone* _currentBone;
for (unsigned int j = 0; j < _currentMesh->mNumBones; j++)
{
_currentBone = _currentMesh->mBones[j];
vector<string>::iterator _result = std::find(_boneIdentities.begin(),_boneIdentities.end(),(const string&)_currentBone->mName);
unsigned short _currentIndex;
if (_result == _boneIdentities.end())
{
_boneIdentities.push_back((const string&)_currentBone->mName);
_boneIndices.push_back((const unsigned short&)_nboneCount);
_currentIndex = _nboneCount;
_nboneCount++;
}
else
{
_currentIndex = _boneIndices[distance(_boneIdentities.begin(), _result)];
}
aiVertexWeight* _currentWeight;
for (unsigned long k = 0; k < _currentBone->mNumWeights; k++)
{
_currentWeight = &_currentBone->mWeights[k];
if (!_vertices[_currentWeight->mVertexId].Weights.x)
{
_vertices[_currentWeight->mVertexId].Weights.x = _currentWeight->mWeight;
_vertices[_currentWeight->mVertexId].Index.x = _currentIndex;
}
else if (!_vertices[_currentWeight->mVertexId].Weights.y)
{
_vertices[_currentWeight->mVertexId].Weights.y = _currentWeight->mWeight;
_vertices[_currentWeight->mVertexId].Index.y = _currentIndex;
}
else if (!_vertices[_currentWeight->mVertexId].Weights.z)
{
_vertices[_currentWeight->mVertexId].Weights.z = _currentWeight->mWeight;
_vertices[_currentWeight->mVertexId].Index.z = _currentIndex;
}
else if (!_vertices[_currentWeight->mVertexId].Index.w)
{
_vertices[_currentWeight->mVertexId].Index.w = _currentIndex;
}
else
{
MessageBox(0, L"A Vertex has more than 4 bones effecting it, the 5th or more bone is not predictable and may lead to horrible deformation of the model apon animation!",
L"ModelImport Error", MB_OK);
}
}
}
}
}
I wrote an animation extractor for assimp that is free to use, check it out http://nolimitsdesigns.com/game-design/open-asset-import-library-animation-loader/

It precomputes all the animations so no work is done at run-time, which means its fast. Hopefully that will help you out.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
thanks loads, I'm assuming for initial interpreting of data the thing of interest would be the cAnimEvaluator's constructor?
and another assumption (which if true I've got all I need to finish the importing) is that mAnimations->mChannels->mNodeName shares a name with the bone(s) its effecting?
In which case all I need to do now is use each mchannels to fill the bones in each frame in each animation. I may need to rework the layout of my animation hierarchy to be more flexible with unsynchronised keyframes but bar that its pretty much done.
I seriously can't thank you enough :)
Use the SceneAnimator class, the other classes are helper classes for it.
Call Init on SceneAnimator, then GetTransforms each frame and pass the array to the vertex shader. Thats pretty much it.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
I want to be able to code it myself to put it into my own structures, so that, should I want to change it for efficiency, functionality, or additional model processing (etc. etc.) I can.
Here is my new revision, it should be testable, I'll say if it works or not and what I needed to fix (I don't mind people seeing my code, the engine will be open source after its first project.)
please let me know if you notice anything I've missed :)


void ::NodeClass::ProcessChannel(aiNodeAnim* p_node)
{
m_positionKeys = map<double,D3DXVECTOR3>();
m_rotationKeys = map<double,D3DXQUATERNION>();
m_scaleKeys = map<double,D3DXVECTOR3>();

{
aiVectorKey* _currentKey;
for (unsigned int i = 0; i < p_node->mNumPositionKeys; i++)
{
_currentKey = &p_node->mPositionKeys;
m_positionKeys[_currentKey->mTime] = D3DXVECTOR3(_currentKey->mValue.x,_currentKey->mValue.y,_currentKey->mValue.z);
}
}
{
aiQuatKey* _currentKey;
for (unsigned int i = 0; i < p_node->mNumRotationKeys; i++)
{
_currentKey = &p_node->mRotationKeys;
m_rotationKeys[_currentKey->mTime] = D3DXQUATERNION(_currentKey->mValue.x,_currentKey->mValue.y,_currentKey->mValue.z,_currentKey->mValue.w);
}
}
{
aiVectorKey* _currentKey;
for (unsigned int i = 0; i < p_node->mNumScalingKeys; i++)
{
_currentKey = &p_node->mScalingKeys;
m_scaleKeys[_currentKey->mTime] = D3DXVECTOR3(_currentKey->mValue.x,_currentKey->mValue.y,_currentKey->mValue.z);
}
}

}

void GetNodeNames(vector<string>* p_identities, vector<pair<unsigned short, unsigned short>>* p_identifiers, unsigned short* p_nnodeCount, aiNode* p_node, unsigned short p_parent)
{
p_identities->push_back(p_node->mName.data);
p_identifiers->push_back(pair<unsigned short, unsigned short>(*p_nnodeCount, p_parent));
unsigned short _parent = *p_nnodeCount;
(*p_nnodeCount)++;
for (unsigned int i = 0; i < p_node->mNumChildren; i++)
{
GetNodeNames(p_identities, p_identifiers, p_nnodeCount, p_node->mChildren, _parent);
}
}
void ::ModelClass::ProcessMesh(const aiScene* scene)
{
ModelPartClass* _modelPart = new ModelPartClass();
ModelMeshClass* _modelMesh = new ModelMeshClass();
ModelMeshPart* _meshParts = new ModelMeshPart[scene->mNumMeshes];
AnimationLibrary* _animationLibrary = new AnimationLibrary();
VertexTypes::Vertex_UV_WEIGHT_NORM_TANGENT* _vertices;
unsigned long* _indices;
vector<string> _nodeIdentities = vector<string>();
vector<pair<unsigned short, unsigned short>> _nodeIndices = vector<pair<unsigned short, unsigned short>>();
unsigned short _nnodeCount = 0;
GetNodeNames(&_nodeIdentities, &_nodeIndices, &_nnodeCount, scene->mRootNode, 0);
aiMesh* _currentMesh;
for (unsigned int i = 0; i < scene->mNumMeshes; i++)
{
_currentMesh = scene->mMeshes;
_vertices = new VertexTypes::Vertex_UV_WEIGHT_NORM_TANGENT[_currentMesh->mNumVertices];
_indices = new unsigned long[_currentMesh->mNumFaces * 3];
for (unsigned long j = 0; j < _currentMesh->mNumVertices; j++)
{
_vertices[j].Position.x = _currentMesh->mVertices[j].x;
_vertices[j].Position.y = _currentMesh->mVertices[j].y;
_vertices[j].Position.z = _currentMesh->mVertices[j].z;

_vertices[j].Normal.x = _currentMesh->mNormals[j].x;
_vertices[j].Normal.y = _currentMesh->mNormals[j].y;
_vertices[j].Normal.z = _currentMesh->mNormals[j].z;

_vertices[j].Tangent.x = _currentMesh->mTangents[j].x;
_vertices[j].Tangent.y = _currentMesh->mTangents[j].y;
_vertices[j].Tangent.z = _currentMesh->mTangents[j].z;

_vertices[j].BiNormal.x = _currentMesh->mBitangents[j].x;
_vertices[j].BiNormal.y = _currentMesh->mBitangents[j].y;
_vertices[j].BiNormal.z = _currentMesh->mBitangents[j].z;

_vertices[j].UV.x = _currentMesh->mTextureCoords[j][0].x;
_vertices[j].UV.y = _currentMesh->mTextureCoords[j][0].y;
}
aiBone* _currentBone;
for (unsigned int j = 0; j < _currentMesh->mNumBones; j++)
{
_currentBone = _currentMesh->mBones[j];
vector<string>::iterator _result = std::find(_nodeIdentities.begin(),_nodeIdentities.end(),(const string&)_currentBone->mName);
unsigned short _currentIndex = _nodeIndices[distance(_nodeIdentities.begin(), _result)].first;
aiVertexWeight* _currentWeight;
for (unsigned long k = 0; k < _currentBone->mNumWeights; k++)
{
_currentWeight = &_currentBone->mWeights[k];
if (!_vertices[_currentWeight->mVertexId].Weights.x)
{
_vertices[_currentWeight->mVertexId].Weights.x = _currentWeight->mWeight;
_vertices[_currentWeight->mVertexId].Index.x = _currentIndex;
}
else if (!_vertices[_currentWeight->mVertexId].Weights.y)
{
_vertices[_currentWeight->mVertexId].Weights.y = _currentWeight->mWeight;
_vertices[_currentWeight->mVertexId].Index.y = _currentIndex;
}
else if (!_vertices[_currentWeight->mVertexId].Weights.z)
{
_vertices[_currentWeight->mVertexId].Weights.z = _currentWeight->mWeight;
_vertices[_currentWeight->mVertexId].Index.z = _currentIndex;
}
else if (!_vertices[_currentWeight->mVertexId].Index.w)
{
_vertices[_currentWeight->mVertexId].Index.w = _currentIndex;
}
else
{
MessageBox(0, L"A Vertex has more than 4 bones effecting it, the 5th or more bone is not predictable and may lead to horrible deformation of the model apon animation!",
L"ModelImport Error", MB_OK);
}
}
}
for (unsigned long j = 0; j < scene->mMeshes->mNumFaces; j++)
{
_indices[(j*3)] = scene->mMeshes->mFaces[j].mIndices[0];
_indices[(j*3)+1] = scene->mMeshes->mFaces[j].mIndices[1];
_indices[(j*3)+2] = scene->mMeshes->mFaces[j].mIndices[2];
}
_meshParts.CreateMeshPartClass(_modelMesh, P2P::system::g_d3dHandle->GetDevice(), _vertices, _currentMesh->mNumVertices, _indices, _currentMesh->mNumFaces * 3);
delete[] _vertices;
_vertices = 0;
delete[] _indices;
_indices = 0;
}
AnimationClass* _animations = new AnimationClass[scene->mNumAnimations];
aiAnimation* _currentAnimation;
for (unsigned int i = 0; i < scene->mNumAnimations; i++)
{
map<unsigned short, NodeClass>* _nodes = new map<unsigned short, NodeClass>();
_currentAnimation = scene->mAnimations;
aiNodeAnim* _currentChannel;
for (unsigned int j = 0; j < _currentAnimation->mNumChannels; j++)
{
_currentChannel = _currentAnimation->mChannels[j];
vector<string>::iterator _result = std::find(_nodeIdentities.begin(),_nodeIdentities.end(),(const string&)_currentChannel->mNodeName);
unsigned short _currentIndex = _nodeIndices[distance(_nodeIdentities.begin(), _result)].first;
unsigned short _parentIndex = _nodeIndices[distance(_nodeIdentities.begin(), _result)].second;
NodeClass _node = NodeClass();
_node.CreateNodeClass(&_animations, _parentIndex, _currentIndex);
_node.ProcessChannel(_currentChannel);
(*_nodes)[_currentIndex] = _node;
}
_animations = AnimationClass();
_animations.CreateAnimationClass(_animationLibrary,_currentAnimation->mName.data, _nodes, _currentAnimation->mTicksPerSecond, _currentAnimation->mDuration);
}
_animationLibrary->CreateAnimationLibrary(_modelPart, _animations);
_modelMesh->CreateMeshClass(_modelPart, _meshParts);
_modelPart->CreateModelPart(this, _modelMesh, _animationLibrary);
this->CreateModelClass(0, _modelPart);
}
okay so now I've fixed bits and bobs and no errors are thrown.
But I'm unable to get things to show up on screen, which is odd, I've got it working with just the modelpart class before.

I've attached my source code in case anyone would like to help, because I really have no information on the problem, no errors thrown by anything no break points, no exceptions, everything runs clean but nothing seems to be drawn.

I'll be fiddling around with it anyway and I'll say if something pops up, I've put in the P2PLib.h file near the top some conventions I use for naming, and accessibility, for the sake of ease of reading (WindowsClass, D3DClass, ShaderClass and MeshPartClass may not be perfect with these conventions as I wrote them months before I wrote the rest.)
It took me ~20 minutes to get your code running on my machine but it did eventually run. Some comments and tips from the experience:
- Never use hardcoded absolute paths as they will make compiling/running your code on another machine painful. You'll run into this when you get other programmers working on your code or start distributing your binaries to other people. There were only a couple of includes and libraries with absolute paths in your project, plus the assets.
- From memory I don't think that using #pragma comment(lib, etc) to specify libraries for linking works with GCC or LLVM. I prefer to have libraries specified in project property sheets (or whatever similar technique your IDE uses for configuring projects). This makes porting much easier too ;)
- Include any libraries that your project needs to compile and run with your project. Fortunately I already have the DX SDK and Assimp on my PC, so this wasn't much of a hassle for me.
- Check the return code of every D3D/D3DX function that you call! I wrap every D3D call with something like the below:

#include <DXErr.h> // also link your project to dxerr.lib
void your_logging_function( char const * category, char const * format, ... ) {}
#define LOG_DX_ERROR( hresult ) your_logging_function( "error", "DX error: %s, %s", DXGetErrorString( hresult ), DXGetErrorDescription( hresult ) );
#define CHECK_DX_RESULT( hresult ) if ( hresult != 0 ) { LOG_DX_ERROR( hresult ); }

Usage:
CHECK_DX_RESULT( _shader.GetEffect()->SetMatrix("world", &_model.worldMatrix) );

This brings me to your first rendering problem. In your main loop:

_shader.GetEffect()->SetMatrix("world", &_model.worldMatrix);
_shader.GetEffect()->SetMatrix("view", _camera.GetView());
_shader.GetEffect()->SetMatrix("proj", _camera.GetProjection());

The handles you are passing here do not match the handles that you are using in your shader. They are case sensitive, so "world" != "World", etc. These functions were returning an error.

Your second problem is with the vector matrix multiplication in your vertex shader:

float4 Position = mul(World, input.Position);
Position = mul(View, Position);
output.Position = mul(Proj, Position);

which needs to be:
float4 Position = mul(input.Position, World);
Position = mul(Position, View);
output.Position = mul(Position, Proj);

I haven't looked into how you're handling your matrices, but they seem to be the transpose of what you're expecting in your shaders. You'll either need to make the change above or find out where your matrices are being transposed.

Keep up the good work smile.png
I honestly can not thank you enough!
I'll run through my code and clean it up, and move dependency copies to the project folder.
I was in such a rush to get things working I skipped a bunch of error checks.
Thanks again :)

This topic is closed to new replies.

Advertisement