Skeletal animation with parenting challenges

tl; dr: When animating the model, each joint moves correctly, but not relative to its parent joint.

enter image description here

I am working on a skeletal animation system using a custom built-in IQE loader and renderer in Lua. Almost everything works at this point, except that the skeleton appears to be disconnected when animating. Each joint translates, rotates and scales correctly, but does not respect the position of its parent, thus creating some terrible problems.

With reference to the IQM spec and demo, I can't for the life of me know what's going wrong. My Lua code is (as far as I can tell) identical to referenced C ++.

Calculation of base matrices:

local base = self.active_animation.base
local inverse_base = self.active_animation.inverse_base

for i, joint in ipairs(self.data.joint) do
    local pose = joint.pq

    local pos = { pose[1], pose[2], pose[3] }
    local rot = matrix.quaternion(pose[4], pose[5], pose[6], pose[7])
    local scale = { pose[8], pose[9], pose[10] }

    local m = matrix.matrix4x4()
    m = m:translate(pos)
    m = m:rotate(rot)
    m = m:scale(scale)

    local inv = m:invert()

    if joint.parent > 0 then
        base[i] = base[joint.parent] * m
        inverse_base[i] = inv * inverse_base[joint.parent]
    else
        base[i] = m
        inverse_base[i] = inv
    end
end

      

Calculating animation frame matrices

local buffer = {}
local base = self.active_animation.base
local inverse_base = self.active_animation.inverse_base
for k, pq in ipairs(self.active_animation.frame[self.active_animation.current_frame].pq) do
    local joint = self.data.joint[k]
    local pose = pq

    local pos = { pose[1], pose[2], pose[3] }
    local rot = matrix.quaternion(pose[4], pose[5], pose[6], pose[7])
    local scale = { pose[8], pose[9], pose[10] }

    local m = matrix.matrix4x4()
    m = m:translate(pos)
    m = m:rotate(rot)
    m = m:scale(scale)

    local f = matrix.matrix4x4()

    if joint.parent > 0 then
        f = base[joint.parent] * m * inverse_base[k]
    else
        f = m * inverse_base[k]
    end

    table.insert(buffer, f:to_vec4s())
end

      

The complete code is here for further study. The relevant code is in /libs/iqe.lua and at the bottom in the IQE: buffer () and IQE: send_frame () functions. This code runs on a custom version of the LOVE game environment as well as Windows binaries (and batch).

Final note: our matrix code has been tested against other implementations and several tests.

+3


source to share


1 answer


The transformations of parental bones should influence the transformation of their children. Indeed, this is achieved by determining the transformation of a particular bone in its structure. Thus, usually the transformation of bones is specified in their local coordinate system, which depends on its parent. If either parent changes, this transformation will affect all children, even if their local transformations have not changed.

In your case, you will cache all absolute (relative to the root, more precisely) transformations of each node. Then you update the local transforms of each node with the cache and don't update your cache. So how would changing the local transform of a node affect its children if you are using the cache instead of the actual parent transform when updating the child?

There is one more problem. Why are you doing the following?

f = base[joint.parent] * m * inverse_base[k]

      

I mean, normally it would be just:

f = base[joint.parent] * m

      



I think the transformations recorded in the animation are absolute (more precisely, relative to the root). It is very strange. Usually, each transformation is local. Check out this problem because it will add a lot of problems for you.

Moreover, I see no need to cache anything in your case (other than inverse_base, which is usually not required).

Modify the IQE: send_frame () function as follows:

local buffer = {}
local transforms = {}
local inverse_base = self.active_animation.inverse_base
for k, pq in ipairs(self.active_animation.frame[self.active_animation.current_frame].pq) do
    local joint = self.data.joint[k]
    local pose = pq

    local pos = { pose[1], pose[2], pose[3] }
    local rot = matrix.quaternion(pose[4], pose[5], pose[6], pose[7])
    local scale = { pose[8], pose[9], pose[10] }

    local m = matrix.matrix4x4()
    m = m:translate(pos)
    m = m:rotate(rot)
    m = m:scale(scale)

    local f = matrix.matrix4x4()

    if joint.parent > 0 then
        transforms[k] = transforms[joint.parent] * m
        f = transforms[k] * inverse_base[k]
    else
        f = m  * inverse_base[k]
        transforms[k] = m
    end

    table.insert(buffer, f:to_vec4s())
end

      

It's good for me. Try to get rid of inverse_base and you should be able to remove all animation related code from IQE: buffer () function

PS As a rule, all nodes are updated by moving around the tree. However, you are updating the nodes by going down the list. You should know that you have to somehow guarantee that for any node it will keep track of it.

+3


source







All Articles