Advertisement

Struggling to implement assimp skeleton into DX12

Started by April 06, 2023 12:30 AM
1 comment, last by Gregm8 1 year, 7 months ago

Recently I have been trying to implement assimp into Frank Luna's basic dx12 engine as part of my learning. I have had real trouble getting the matrix mathematics working correctly, and have hit a bit of a dead end.

This is the main function I am using to get the transformations from the skeleton and animations to pass across to the GPU.

First things first, there is a github link here for anyone who just prefers to look straight at the source: repo

void Skeleton::GetTransforms(float timePos, aiNode* node, aiAnimation* animation, aiMatrix4x4& parentTransform, const aiMatrix4x4& globalInverseTransform, std::vector<DirectX::XMFLOAT4X4>& transforms)
{
    std::string nodeName(node->mName.data);
    aiMatrix4x4 nodeTransform = node->mTransformation;
    const aiNodeAnim* nodeAnim = FindNodeAnim(animation, nodeName);

    if (nodeAnim)
    {
        aiVector3D scaling;
        aiQuaternion rotation;
        aiVector3D translate;

        CalcInterpolatedScaling(scaling, timePos, nodeAnim);
        CalcInterpolatedRotation(rotation, timePos, nodeAnim);
        CalcInterpolatedPosition(translate, timePos, nodeAnim);

        nodeTransform = CreateAffineMatrix(scaling, rotation, translate);
    }

    aiMatrix4x4 globalTransform = parentTransform * nodeTransform;

    if (this->bones.find(nodeName) != this->bones.end())
    {
        int boneIndex = this->bones[nodeName].index;
        aiMatrix4x4 finalTransform = globalInverseTransform * globalTransform * this->bones[nodeName].offsetMatrix;
        aiMatConvert(finalTransform, transforms[boneIndex]);
    }

    for (UINT i = 0u; i < node->mNumChildren; i++)
    {
        GetTransforms(timePos, node->mChildren[i], animation, globalTransform, globalInverseTransform, transforms);
    }
}

This code aligns closely with what I have found on several other threads:

Unfortunately this is no producing the correct result, currently this is producing a weird result as you can see below:

Broken Result

If I disable animation by commenting out nodeTransform = CreateAffineMatrix(scaling, rotation, translate);

The result is as I would expect, the character sits at TPose:

Result without animation transforms

I have not been able to figure out what about my code base is not working correctly, these are some of the methods I have already tried to resolve the problem:

  • Transposing the result
  • Transposing the offsetMatrix
  • Using .glb and .gltf instead of fbx
  • SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false)
  • Using a simple example, ie a box with 3 or 4 joints

One observation that could be helpful, I have noticed that this problem does not occur for joints that are oriented to world space. So essentially they don't have a unique orientation. This leads me to think that something about the globalInverseTransform * globalTransform * this->bones[nodeName].offsetMatrix may not be working correctly.

I am sourcing the globalInverseTransform with this function:

struct Animation
{
    Skeleton* skeleton;
    aiNode* rootNode;
    aiAnimation* animation;
    std::vector<DirectX::XMFLOAT4X4> transforms;
    float TimePos = 0.0f;
    bool Loop = true;
    float Speed = 1.0f;

    void UpdateSkinnedAnimation(float dt)
    {
        dt *= Speed;
        TimePos += dt;

        if (Loop)
        {
            float duration = animation->mDuration;
            if (TimePos > duration)
            {
                TimePos = 0.0;
            }
        }
        
        skeleton->GetTransforms(TimePos, rootNode, animation, aiMatrix4x4(), rootNode->mTransformation.Inverse(), transforms);

    }
};

The rootNode pointer is set during the mesh loading stage, and data is stored as heap memory so it does not loose its scope at runtime.

The rest of the skeleton related functions are list below,

Thanks a bunch ! This really has had me stumped for a good week now, not exactly sure which way to look next, some help would be greatly appreciated! :)

float clamp(const float& f)
{
    return (f < 0.0f) ? 0.0f : ((f > 1.0f) ? 1.0f : f);
}

void aiMatConvert(const aiMatrix4x4& aiMatrix, DirectX::XMFLOAT4X4& dXMatrix)
{
    DirectX::XMMATRIX meshToBoneTransform = DirectX::XMMATRIX(aiMatrix.a1, aiMatrix.a2, 
                                            aiMatrix.a3, aiMatrix.a4, aiMatrix.b1, aiMatrix.b2, 
                                            aiMatrix.b3, aiMatrix.b4, aiMatrix.c1, aiMatrix.c2, 
                                            aiMatrix.c3, aiMatrix.c4, aiMatrix.d1, aiMatrix.d2, 
                                            aiMatrix.d3, aiMatrix.d4);

    DirectX::XMStoreFloat4x4(&dXMatrix, meshToBoneTransform);
}

const aiNodeAnim* Skeleton::FindNodeAnim(const aiAnimation* animation, const std::string& nodeName)
{
    for (unsigned int i = 0; i < animation->mNumChannels; ++i)
    {
        const aiNodeAnim* nodeAnim = animation->mChannels[i];
        if (std::string(nodeAnim->mNodeName.data) == nodeName)
        {
            return nodeAnim;
        }
    }

    return nullptr;
}

int Skeleton::FindPositionKey(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    for (int i = 0; i < pNodeAnim->mNumPositionKeys - 1; i++) {
        if (AnimationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
            return i;
        }
    }
    return 0;
}


int Skeleton::FindRotationKey(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    for (int i = 0; i < pNodeAnim->mNumRotationKeys - 1; i++) {
        if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
            return i;
        }
    }
    return 0;
}

int Skeleton::FindScalingKey(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    for (int i = 0; i < pNodeAnim->mNumScalingKeys - 1; i++) {
        if (AnimationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
            return i;
        }
    }
    return 0;
}

void Skeleton::CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    if (pNodeAnim->mNumPositionKeys == 1) {
        Out = pNodeAnim->mPositionKeys[0].mValue;
        return;
    }

    int PositionIndex = FindPositionKey(AnimationTime, pNodeAnim);
    int NextPositionIndex = (PositionIndex + 1);
    
    float DeltaTime = (float)(pNodeAnim->mPositionKeys[NextPositionIndex].mTime - pNodeAnim->mPositionKeys[PositionIndex].mTime);
    float Factor = clamp((AnimationTime - (float)pNodeAnim->mPositionKeys[PositionIndex].mTime) / DeltaTime);
   
    const aiVector3D& Start = pNodeAnim->mPositionKeys[PositionIndex].mValue;
    const aiVector3D& End = pNodeAnim->mPositionKeys[NextPositionIndex].mValue;
    aiVector3D Delta = End - Start;
    
    Out = Start + Factor * Delta;
}

void Skeleton::CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    if (pNodeAnim->mNumRotationKeys == 1) {
        Out = pNodeAnim->mRotationKeys[0].mValue;
        return;
    }

    int RotationIndex = FindRotationKey(AnimationTime, pNodeAnim);
    int NextRotationIndex = (RotationIndex + 1);
    
    float DeltaTime = (float)(pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime);
    float Factor = clamp((AnimationTime - (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime);
    
    const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
    const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
    aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
    
    Out = Out.Normalize();
}

void Skeleton::CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    if (pNodeAnim->mNumScalingKeys == 1) {
        Out = pNodeAnim->mScalingKeys[0].mValue;
        return;
    }

    int ScalingIndex = FindScalingKey(AnimationTime, pNodeAnim);
    int NextScalingIndex = (ScalingIndex + 1);
    
    float DeltaTime = (float)(pNodeAnim->mScalingKeys[NextScalingIndex].mTime - pNodeAnim->mScalingKeys[ScalingIndex].mTime);
    float Factor = clamp((AnimationTime - (float)pNodeAnim->mScalingKeys[ScalingIndex].mTime) / DeltaTime);
    
    const aiVector3D& Start = pNodeAnim->mScalingKeys[ScalingIndex].mValue;
    const aiVector3D& End = pNodeAnim->mScalingKeys[NextScalingIndex].mValue;
    aiVector3D Delta = End - Start;
    
    Out = Start + Factor * Delta;
}

aiMatrix4x4 CreateAffineMatrix(const aiVector3D& scaling, const aiQuaternion& rotation, const aiVector3D& translate)
{
    aiMatrix4x4 scalingMatrix;
    aiMatrix4x4 translationMatrix;
    aiMatrix4x4::Scaling(scaling, scalingMatrix);
    aiMatrix4x4::Translation(translate, translationMatrix);
    aiMatrix4x4 rotationMatrix = aiMatrix4x4(rotation.GetMatrix());

    return scalingMatrix * rotationMatrix * translationMatrix;
}

void Mesh::ReadSkeleton(const aiScene* scene, Skeleton* mSkeleton)
{
    unsigned int numMesh = scene->mNumMeshes;
    aiMesh** meshList = scene->mMeshes;

    int boneCount = 0;
    for (UINT x = 0; x < numMesh; ++x)
    {
        for (UINT i = 0; i < meshList[x]->mNumBones; ++i)
        {
            std::string boneName(meshList[x]->mBones[i]->mName.C_Str());
            bool exists = mSkeleton->bones.find(boneName) != mSkeleton->bones.end();

            if (!exists)
            {
                aiMatrix4x4& offsetMatrix = meshList[x]->mBones[i]->mOffsetMatrix;
                Joint joint(boneName, boneCount, offsetMatrix);
                mSkeleton->bones[boneName] = joint;
                boneCount += 1;
            }
        }
    }
}

void Mesh::ReadAnimations(const aiScene* scene, std::unordered_map<std::string, aiAnimation*> animations)
{
    unsigned int numAnim = scene->mNumAnimations;

    for (UINT x = 0; x < numAnim; ++x)
    {
        animations[scene->mAnimations[x]->mName.C_Str()] = scene->mAnimations[x];
    }
}

Resolved this, there is an issue with the latest version of assimp causing this, reverted to assimp 5.0 and its working as expected now

This topic is closed to new replies.

Advertisement