apply non-hierarchial transforms to hierarchial skeleton?

1.7k views Asked by At

I use Blender3D, but the answer might not API-exclusive.

I have some matrices I need to assign to PoseBones. The resulting pose looks fine when there is no bone hierarchy (parenting) and messed up when there is. enter image description here

I've uploaded an archive with sample blend of the rigged models, text animation importer and a test animation file here: http://www.2shared.com/file/5qUjmnIs/sample_files.html
Import the animation by selecting an Armature and running the importer on "sba" file. Do this for both Armatures.

This is how I assign the poses in the real (complex) importer:

matrix_bases = ... # matrix from file
animation_matrix = matrix_basis * pose.bones['mybone'].matrix.copy()
pose.bones[bonename].matrix = animation_matrix

If I go to edit mode, select all bones and press Alt+P to undo parenting, the Pose looks fine again.

The API documentation says the PoseBone.matrix is in "object space", but it seems clear to me from these tests that they are relative to parent bones.

Final 4x4 matrix after constraints and drivers are applied (object space)

I tried doing something like this:

matrix_basis = ... # matrix from file
animation_matrix = matrix_basis * (pose.bones['mybone'].matrix.copy()  * pose.bones[bonename].bone.parent.matrix_local.copy().inverted())
pose.bones[bonename].matrix = animation_matrix

But it looks worse. Experimented with order of operations, no luck with all.

For the record, in the old 2.4 API this worked like a charm:

matrix_basis = ... # matrix from file
animation_matrix = armature.bones['mybone'].matrix['ARMATURESPACE'].copy() * matrix_basis
pose.bones[bonename].poseMatrix = animation_matrix

pose.update()

Link to Blender API ref:

http://www.blender.org/documentation/blender_python_api_2_63_17/bpy.types.BlendData.html#bpy.types.BlendData

http://www.blender.org/documentation/blender_python_api_2_63_17/bpy.types.PoseBone.html#bpy.types.PoseBone

1

There are 1 answers

1
user1615873 On

'object space' probably does mean relative to the parent bone. You can convert from global to local by multiplying times the inverse of the parent transform's matrix. You may also find that you'll want to multiply by the concatenation of all parent inverse transforms: multiply B1 * inverse(B0), and B2 * (inverse(B1) * inverse(B0)).

Here's some example code that does something similar (in Panda3D, not Blender, but same general idea). We start off with 3 bones with global position and rotation values, parent them together, and convert the global coordinates into the correct local matrices.

    # Load three boxes ('bones'), give them global position and rotation
    # each is 3 units long, at a 30 degree angle.  

    self.bone1=loader.loadModel("box.egg")
    self.bone1.reparentTo(render)

    self.bone2=loader.loadModel("box.egg")
    self.bone2.reparentTo(self.bone1)

    self.bone3=loader.loadModel("box.egg")
    self.bone3.reparentTo(self.bone2)

    ''' 
    equivalent code, in local coordinates
    self.bone1.setPos(0,0,0)
    self.bone1.setHpr(0,0,30)

    self.bone2.setPos(0,0,3)
    self.bone2.setHpr(0,0,30)

    self.bone3.setPos(0,0,3)
    self.bone3.setHpr(0,0,30)
    '''

    # give each a global rotation value

    R1=Mat4()
    R1.setRotateMat(30,Vec3(0,1,0))

    R2=Mat4()
    R2.setRotateMat(60,Vec3(0,1,0))

    R3=Mat4()
    R3.setRotateMat(90,Vec3(0,1,0))

    # set global translation values

    T1=Mat4()

    # position of bone 2 in global coords
    T2 = Mat4.translateMat(1.271,0,2.606) 

    # position of bone 3 in global coords
    T3 = Mat4.translateMat(3.782,0,4.036) 

    # set the matrix for bone 1
    M1 = R1 * T1
    self.bone1.setMat(M1)

    # get inverse of matrix of parent
    I1 = Mat4()
    I1.invertFrom (M1)

    # multiply bone2 matrix times inverse of parent

    M2 = R2 * T2
    M2 = M2 * I1

    self.bone2.setMat(M2)

    # get inverse of parent for next bone 

    I2 = Mat4()
    I2.invertFrom(M2)

    M3 = R3 * T3
    # notice that M3 * I2 isn't enough - needs to be M3 * (I1 * I2)
    M3 =  M3 * (I1 * I2)

    self.bone3.setMat(M3)