6
\$\begingroup\$

I'm trying to implement skeletal animation using assimp and glm. Everything seems to work, except for rotations.

This is the code I use when packing assimp data into my own engine's format. I THINK it should be correct, but since I've already run into problems with matrices (assimp uses row major, glm - column major), I'd rather post this, too:

for(uint32_t l = 0; l < tempChannel.numRotationKeys; ++l){ quatKey tempKey; tempKey.values = glm::quat(scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.w, scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.x, scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.y, scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.z); 

And this is the code that performs the actual transformations (quite a bit of it is based on http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html), but modifications (e.g. storing a list, where each child element is placed after a parent and keeps a parent's id, as was suggested here: http://blog.tojicode.com/2011/10/building-game-part-3-skinning-animation.html) have been made. Scaling and location transforms seem to be working fine, but rotations don't:

for(unsigned int i = 0; i < skeleton.size(); ++i){ finalTransforms[i] = calcFinalTransform(time, i); } glm::mat4 SkeletalMeshObject::calcFinalTransform(double animationTime, unsigned int currentNode){ // Find the node where the keyframes for the current bone are stored // Return -1, if not found int nodeId = findNodeAnimation(currentAnimation, skeleton[currentNode].name); glm::mat4 nodeTransform(skeleton[currentNode].offset); // If this node isn't animated, pass the default offset to it if(nodeId >= 0){ unsigned int index; unsigned int nextIndex; // Interpolate scaling glm::vec3 scl; if(animations[currentAnimation].channels[nodeId].scalingKeys.size() == 1){ scl = animations[currentAnimation].channels[nodeId].scalingKeys[0].values; } else { index = findKey(animationTime, KeyType::Scaling, nodeId); nextIndex = index + 1; double delta = animations[currentAnimation].channels[nodeId].scalingKeys[nextIndex].time - animations[currentAnimation].channels[nodeId].scalingKeys[index].time; double factor = (animationTime - animations[currentAnimation].channels[nodeId].scalingKeys[index].time) / delta; scl = glm::mix(animations[currentAnimation].channels[nodeId].scalingKeys[index].values, animations[currentAnimation].channels[nodeId].scalingKeys[nextIndex].values, (float)factor); } // Interpolate rotation glm::quat rot; if(animations[currentAnimation].channels[nodeId].rotationKeys.size() == 1){ rot = animations[currentAnimation].channels[nodeId].rotationKeys[0].values; } else { index = findKey(animationTime, KeyType::Rotation, nodeId); nextIndex = index + 1; double delta = animations[currentAnimation].channels[nodeId].rotationKeys[nextIndex].time - animations[currentAnimation].channels[nodeId].rotationKeys[index].time; double factor = (animationTime - animations[currentAnimation].channels[nodeId].rotationKeys[index].time) / delta; rot = glm::mix(animations[currentAnimation].channels[nodeId].rotationKeys[index].values, animations[currentAnimation].channels[nodeId].rotationKeys[nextIndex].values, (float)factor); } // Interpolate location glm::vec3 loc; if(animations[currentAnimation].channels[nodeId].locationKeys.size() == 1){ loc = animations[currentAnimation].channels[nodeId].locationKeys[0].values; } else { index = findKey(animationTime, KeyType::Location, nodeId); nextIndex = index + 1; double delta = animations[currentAnimation].channels[nodeId].locationKeys[nextIndex].time - animations[currentAnimation].channels[nodeId].locationKeys[index].time; double factor = (animationTime - animations[currentAnimation].channels[nodeId].locationKeys[index].time) / delta; loc = glm::mix(animations[currentAnimation].channels[nodeId].locationKeys[index].values, animations[currentAnimation].channels[nodeId].locationKeys[nextIndex].values, (float)factor); } // Get final transform nodeTransform = glm::translate(loc) * glm::toMat4(rot) * glm::scale(scl); } // Store the node's transform locRotScaleTransforms[currentNode] = nodeTransform; // Get the id of the parent int parentId = skeleton[currentNode].parent; // Hierarchial transform glm::mat4 hierarchyTransform = nodeTransform; while(parentId >= 0){ hierarchyTransform = locRotScaleTransforms[parentId] * hierarchyTransform; parentId = skeleton[parentId].parent; } // Apply inverse bind position matrix hierarchyTransform *= skeleton[currentNode].ibp; return hierarchyTransform; } 

Here's how the animation looks in blender: And here's in my engine. Couldn't capture a gif from my window, so I'll describe it. First, it rotates along multiple axes (bones in the original animation were constrained to z axis), and as you can also see, it distorts. The frame seen below is almost at an end of animation, when distortions are most visible:

What can I do to make it look right?

\$\endgroup\$
1
  • \$\begingroup\$ Your quaternion to matrix code might be a bit off, since AssImp's matrices are "flipped". \$\endgroup\$ Commented Jul 8, 2016 at 14:24

2 Answers 2

2
\$\begingroup\$

First thing I see is that you shouldn't read the quaternion in reverse order.

Also you shouldn't use glm::mix, use glm::slerp instead.

And here is how I construct the bone transform:

mat = glm::mat4_cast( currentrotation ); mat[0][0] *= currentscale.x; mat[1][0] *= currentscale.x; mat[2][0] *= currentscale.x; mat[0][1] *= currentscale.y; mat[1][1] *= currentscale.y; mat[2][1] *= currentscale.y; mat[0][2] *= currentscale.z; mat[1][2] *= currentscale.z; mat[2][2] *= currentscale.z; mat[0][3] = currentposition.x; mat[1][3] = currentposition.y; mat[2][3] = currentposition.z; 

Also I would check all my matrix multiplication, it should be in reverse order to how would you do it with Assimp matrices.


I base all this on personal experience from implementing an animated mesh with assimp and glm myself.(which now works) But it might not be the correct way of doing things.

\$\endgroup\$
2
  • \$\begingroup\$ I'm almost sure I'm reading the quaternion in the right order. Here's how glm defines the constructor: glm.g-truc.net/0.9.4/api/a00076_source.html#l00068 \$\endgroup\$ Commented Dec 20, 2013 at 15:52
  • \$\begingroup\$ @Manvis You are correct, I should have double checked. Edited answer. \$\endgroup\$ Commented Dec 20, 2013 at 15:58
0
\$\begingroup\$

I think the missing piece is parents starting transform. Meaning the bone was not at the origin to start with, so it should not be animated as it is. I saw you are using parents "animation" transform in the end withing while, but where is the starting point of the bone?

I am saving the transforms while loading the mesh like this:

void ModelAsset::createMeshes(const aiScene *scene, aiNode *aiNode, glm::mat4 parentTransform) { parentTransform = parentTransform * GLMConverter::AssimpToGLM(aiNode->mTransformation); for (unsigned int i = 0; i < aiNode->mNumMeshes; ++i) { aiMesh *currentMesh; currentMesh = scene->mMeshes[aiNode->mMeshes[i]]; for (unsigned int j = 0; j < currentMesh->mNumBones; ++j) { meshOffsetmap[currentMesh->mBones[j]->mName.C_Str()] = GLMConverter::AssimpToGLM( currentMesh->mBones[j]->mOffsetMatrix); std::string boneName = currentMesh->mBones[j]->mName.C_Str(); boneName += "_parent"; meshOffsetmap[boneName] = parentTransform; } //some other code for (unsigned int i = 0; i < aiNode->mNumChildren; ++i) { createMeshes(scene, aiNode->mChildren[i], parentTransform); } } 

and when I am calculating the animation:

 transforms[boneNode->boneID] = globalInverseTransform * meshOffsetmap.at(boneNode->name + "_parent") * nodeTransform * meshOffsetmap.at(boneNode->name) 

If you would like to check out t Full source code can be found at https://github.com/enginmanap/limonEngine/blob/master/src/Assets/ModelAsset.cpp

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.