I have some problems animating a loaded COLLADA Model. I’ve written my own parser and now I also want to write my own draw routine as well. The problem ist, that as soon as I enable the animation on my model, the hands, legs and the head is stretched away from the origin of the model. (The loader is implemented based on the tutorial here: COLLADA Tutorial)
The first thing I do in my draw function of the model is setup the joints matrices (not it’s world matrices!) with the given targets from the read blocks, If I for example read a channel like:
<channel source="#some_sampler" target="some_joint/transform(3)(2)"/>
I will modify the matrix component (3)(2) from the joint’s jointMatrix with the sid=“transform” in this first step:
if( mCurrentAnimations_.size() > 0 ) {
unsigned currentFrame = GEAR::Root::getSingleton().getFrameEvent().frame;
bool updateTime = false;
if( currentFrame != mLastFrameUpdate_ ) {
if( timeSinceLastFrame < 1.0f )
updateTime = true;
mLastFrameUpdate_ = currentFrame;
}
/****************************************************
* If we have an active animation, *
* we animate it in each of it's defined channels *
***************************************************/
std::list<DAEAnimation*>::iterator it = mCurrentAnimations_.begin();
while( it != mCurrentAnimations_.end() ) {
for( int c = 0; c < (*it)->animation->channels.size(); ++c ) {
// update the time of the channelanimation if requested
if( updateTime ) {
(*it)->channelStates[c].elapsedTime += timeSinceLastFrame;
}
GEAR::COLLADA::Channel* channel = (*it)->animation->channels[c];
// read the two indices depending on the time we're
int firstKeyframeTimeIndex = 0;
int secondKeyframeTimeIndex = 0;
for( int i = 0; i < channel->sampler->inputSource->mFloatArray_->mCount_; ++i ) {
float time = channel->sampler->inputSource->mFloatArray_->mFloats_[i];
if( firstKeyframeTimeIndex == secondKeyframeTimeIndex && time > (*it)->channelStates[c].elapsedTime && i > 0) {
firstKeyframeTimeIndex = i-1;
secondKeyframeTimeIndex = i;
break;
}
if( firstKeyframeTimeIndex == secondKeyframeTimeIndex && i == channel->sampler->inputSource->mFloatArray_->mCount_-1 ) {
(*it)->channelStates[c].elapsedTime = 0.0f;
firstKeyframeTimeIndex = i;
secondKeyframeTimeIndex = 0;
break;
}
}
// look what kind of TargetAccessor we have
if( channel->targetAccessor != NULL && channel->targetAccessor->type == GEAR::MATRIX_ACCESSOR ) {
// ok we have to read 1 value for first and second index
float firstValue = channel->sampler->outputSource->mFloatArray_->mFloats_[firstKeyframeTimeIndex];
float secondValue = channel->sampler->outputSource->mFloatArray_->mFloats_[secondKeyframeTimeIndex];
float firstTime = channel->sampler->inputSource->mFloatArray_->mFloats_[firstKeyframeTimeIndex];
float secondTime = channel->sampler->inputSource->mFloatArray_->mFloats_[secondKeyframeTimeIndex];
float interpolateValue = 1.0f / (secondTime - firstTime) * (secondTime - (*it)->channelStates[c].elapsedTime);
// now we calculate an linear interpolated value
float value = (secondValue*interpolateValue) + (firstValue*(1.0-interpolateValue));
// now we have to write this value to the Joint's Matrix
int entry = ((COLLADA::MatrixTargetAccessor*)channel->targetAccessor)->firstAccessor*4+((COLLADA::MatrixTargetAccessor*)channel->targetAccessor)->secondAccessor;
channel->targetJoint->matrix->jointSpaceMatrix.entries[entry] = channel->targetJoint->matrix->matrix.entries[entry] + value;
}
}
++it;
}
}
After the jointMatrices are modified by all channels, I recalculate the joint’s worldMatrices by calling the following function on the root Joint:
void
COLLADA::Joint::recalcWorldSpaceTransMat() {
GEAR::Mat4 parentMat;
if( parent != NULL )
parentMat = parent->worldSpaceTransformationMatrix;
// @todo Here we have to test against NULL!
if( matrix != NULL )
this->worldSpaceTransformationMatrix = parentMat * matrix->jointSpaceMatrix;
else {
this->worldSpaceTransformationMatrix = parentMat;
}
//std::cout << "Joint " << sid << " recalculated
";
for( int i = 0; i < mChildJoints_.size(); ++i )
mChildJoints_[i]->recalcWorldSpaceTransMat();
}
Now everything should be ready to draw my model width the following last part of my draw function:
for( int i = 0; i < mSubMeshes_.size(); ++i ) {
for( int k = 0; k < mSubMeshes_[i]->mSubMeshes_.size(); ++k ) {
// first we animate it
GEAR::DAESubMesh* submesh = mSubMeshes_[i]->mSubMeshes_[k];
submesh->buffer->lock( true );
{
for( unsigned v = 0; v < submesh->buffer->getNumVertices(); ++v ) {
// get the array of joints, which influence the current vertex
DAEVertexInfo* vertexInfo = submesh->vertexInfo[v];
GEAR::Vec3 vertex; // do not init the vertex with any value!
float totalWeight = 0.0f;
for( int j = 0; j < vertexInfo->joints.size(); ++j ) {
Mat4& invBindPoseMatrix = vertexInfo->joints[j]->joint->invBindPoseMatrix;
Mat4& transMat = vertexInfo->joints[j]->joint->worldSpaceTransformationMatrix;
totalWeight += vertexInfo->joints[j]->weight;
vertex += (transMat*invBindPoseMatrix*(submesh->skin->bindShapeMatrix*vertexInfo->vertex))*vertexInfo->joints[j]->weight;
}
if( totalWeight != 1.0f ) {
float normalizedWeight = 1.0f / totalWeight;
vertex *= normalizedWeight;
}
submesh->buffer->bufferVertexPos( v, vertex );
}
}
submesh->buffer->unlock();
mSubMeshes_[i]->mSubMeshes_[k]->buffer->draw( GEAR::TRIANGLES, 0, mSubMeshes_[i]->mSubMeshes_[k]->buffer->getNumVertices() );
}
}
Now The problem is, that the output looks like the following:
I’m sure to have the data loading routine implemented right, because the general animation of the walking man is visible, but the mesh is deformed:
As I said, when I uncomment the line:
channel->targetJoint->matrix->jointSpaceMatrix.entries[entry] = channel->targetJoint->matrix->matrix.entries[entry] + value;
The animation is disabled and the model is displayed in it’s standard pose:
Anyone, who has an idea about what’s going wrong?