9
$\begingroup$

I have this .blend file which has a simple scene, and a python script. There's a readme section in the script, but to give a general idea:

There are two identical meshes in the scene, one skinned, and the other unskinned. The skinned mesh has a 20 frame animation. If you move to any frame of the animation, and then run the script, the script will modify the position of the unskinned mesh's vertices, to match the vertex positions of the skinned mesh, for the current frame of animation.

The formula I use to calculate the new vertex positions is:

nv = nv + ((boneWeight / TW) * ( bms[boneName] * (ibps[boneName] * v.co) ))

Where:

  • boneWeight: weight of the current bone that influences this vertex
  • TW: total weight of all bones that affect this vertex
  • bms[boneName]: bind pose of the current bone (bpy.data.objects[ARMATURE_NAME].pose.bones[BONE_NAME].matrix)
  • ibps[boneName]: inverted bind pose of the current bone (bpy.data.objects[ARMATURE_NAME].data.bones[BONE_NAME].matrix_local.inverted())
  • v.co: the original vertex coordinates

It's worth noting that, in my Python script, I do not traverse the hierarchy of bones to calculate the correct transform matrices; simply accessing the pose matrix and inverted bind pose matrix for the current bone is enough.

So with this figured out, I now want to export the bind pose and inverted bind pose of every bone, for every keyframe in this animation. In order to keep my exported file size to a minimum, I only want to export the keyframes of an animation, and have my game engine generate the bind pose and inverted bind pose of all bones for all non-keyframes in the animation.

I have found the interpolation functions for Matrices in Blender's source code, and I have re-implemented them in Java. For the root bone of an armature, the matrices that my engine generates for the bind pose and inverted bind pose are correct for all frames in my animation.

However, for non-root bones, the bind pose and inverted bind pose are incorrect, i.e. they do not match the pose_bone.matrix and data_bone.matrix_local.inverted() readouts that Blender gives in the python console.

So, it seems to me that, it isn't enough to export the bind pose and inverted bind pose matrices, and interpolate between them for given keyframes.

Instead, I either need to:

  • Export and interpolate between the matrices/vectors from which the bind pose and inverted bind pose are derived from, and then multiply them all together to give the bind pose and inverted bind pose of the bone on the engine side

OR

  • I continue to export the bind pose and inverted bind pose from blender, but after I have generated the matrices on the engine side, for non-root bones, I multiply the generated matrix with a matrix/inverted matrix belonging to that bone's parent (but what parent bone matrix should I multiply against?)

Essentially, I need to know how, using the Python API in Blender, I can generate a matrix equivalent to:

bpy.data.objects['Armature'].data.pose.bones['Bone.001'].matrix

Without directly accessing bpy.data.objects['Armature'].data.pose.bones['Bone.001'].matrix, if that makes sense?

So, I need to know, step-by-step, what child/parent bone matrices (available via the Python API) to multiply together, to give a matrix equal to the bind pose (bpy.data.objects['Armature'].data.pose.bones['Bone.001'].matrix) of a given bone.

I've been linked to this diagram before: Diagram

But I wasn't able to follow along with it using only the Python API, as I was only able to find the equivalent functions for each step in the C code, not the Python API.

$\endgroup$
  • 1
    $\begingroup$ Hello fergal, you are trying to engineer a new 3D skeleton animation export format and import that into a custom Java engine. Pls do yourself a favor and just don't do this. Use some common armature format like .bvh and implement that into your engine, or better use already available engine - making one yourself is a waste of time. $\endgroup$ – Jaroslav Jerryno Novotny Jan 14 '16 at 14:02
  • 3
    $\begingroup$ I thoroughly disagree. Making your own game engine is a lot of fun, and a magnificent learning exercise. Yes, I have created my own JSON-based export format, and I am trying to add support for animation exports. In order to correctly generate the transform matrices, I need to know what matrices available via the Python API I can multiply together to create a matrix equivalent to the pose bone matrix. If you cannot answer my question, that's fine, but please don't tell me this is a waste of time - it's far from it. $\endgroup$ – fergal Jan 14 '16 at 14:39
  • $\begingroup$ sorry it offended you, but nowadays such thing is like inventing your own alphabet to write a book, or like building a computer from transistors to create a game. It's just better to use stuff that is available and that others understand. I am not blaming you, I wrote some game engines myself and was really into it when I had free time on my hands. I'll look into the pose bone matrix problem. $\endgroup$ – Jaroslav Jerryno Novotny Jan 14 '16 at 14:52
7
+50
$\begingroup$
 armature.data.bones["Bone"].matrix_local

Is the bone's world un-posed matrix (the pose_bone.matrix in rest state).

 armature.pose.bones["Bone"].matrix_basis

Is the bone's local pose matrix.

From there we can calculate any bone's world matrix by recursively traversing it's parents. Note: This will not include any constraints or IK (but it will include drivers or actions):

def matrix_world(armature_ob, bone_name):
    local = armature_ob.data.bones[bone_name].matrix_local
    basis = armature_ob.pose.bones[bone_name].matrix_basis

    parent = armature_ob.pose.bones[bone_name].parent
    if parent == None:
        return  local * basis
    else:
        parent_local = armature_ob.data.bones[parent.name].matrix_local
        return matrix_world(armature_ob, parent.name) * (parent_local.inverted() * local) * basis

You can test it on an empty and some armature with some active pose_bone. Also the armature_object's transformation is accounted here:

import bpy

empty_ob = bpy.data.objects["Empty"]
armature_ob = bpy.data.objects["Armature"]

bone_name = bpy.context.active_pose_bone.name
empty_ob.matrix_world = armature_ob.matrix_world * matrix_world(armature_ob, bone_name )
$\endgroup$
  • 1
    $\begingroup$ thanks a million mate, works a treat - no offense taken! $\endgroup$ – fergal Jan 19 '16 at 0:55
4
$\begingroup$

Introducing blender's different bone matrices:

this code gives the bone's 4*4 matrix in object(armature) coordinates:

bpy.context.active_pose_bone.matrix

Current matrix of the bone (after all constraints) in object(armature) space

this code gives you the rest pose matrix in object(Armature) space:

bpy.context.active_pose_bone.bone.matrix_local

Rest pose matrix in object(Armature) space

Note that this matrix didn't change after rotation(transformation).

and this one gives you the current transformation matrix relative to the rest pose in local coordinates (matrix_local):

bpy.context.active_pose_bone.matrix_basis

transformation matrix relative to the rest pose in local coordinates

Parent and world spaces:

In this picture the red monkey is green monkey's child and you see the world matrix of each one(notice the translation part which is the same as location):

Red monkey is the child of green monkey

because the parent's matrix is just identity the world matrix of the child in this configuration is equivalent to child matrix in the parent space:

local and world matrices of the child when parent is at rest

now if the parent transform to somewhere and some rotation, its world matrix times child's local matrix gives the child's world matrix:

\begin{equation} P:parent \space matrix\\ C:child \space matrix\\ subscripts: coordinate \space system \\ \end{equation} \begin{equation} C_{world} = P_{world} \times C_{parent}\\ \\ \implies P_{world}^{-1} \times C_{world} = P_{world}^{-1} \times P_{world} \times C_{parent} \\ \implies C_{parent} = P_{world}^{-1} \times C_{world} \end{equation}

Interpretation of matrix multiplication

Converting a transformation in some coordinate system into its counterpart in world coordinate:

now when we want to calculate a transformation in another coordinateSystem 'S'(for example parent coords) first we should go to that coordsys, then apply transformation in that coordsys and then comeback to world coordsys as below:

X: Object's transformation matrix
S: A typical coordinate system's matrix (for example parent)
T: Transformation matrix ($T_{s}$ is transformation relative to S coordsys)
subscripts: coordinate system

\begin{equation} X_{s} = S_{world}^{-1} \times X_{world}\\ \end{equation}

martix of X in S system after applying becomes:

\begin{equation} T_{s} \times X_{s} = T_{s} \times (S_{world}^{-1} \times X_{world}) \end{equation}

and now we should turn back to world system:

\begin{equation} S_{world} \times T_{s} \times X_{s} = S_{world} \times T_{s} \times (S_{world}^{-1} \times X_{world})\\ \implies T_{world} \times X_{world} = S_{world} \times T_{s} \times S_{world}^{-1} \times X_{world} \end{equation}

thus the form of transformation T in world coordsys becomes

\begin{equation} T_{world} = S_{world} \times T_{s} \times S_{world}^{-1} \end{equation}

this is how Jaroslav Jerryno Novotny's above code works. in each step it goes to parent space(by multiplying its .bone.matrix_local.inverse()), calculates the rotation in parent's rest(local) space(by multiplying .matrix_basis) and then comes back to armature's coordsys(by multiplying .bone.matrix_local).

but its simpler to use .matrix for calculating a bone's world matrix. you can simply use below function and apply it on an empty:

import bpy

def worldMatrix(ArmatureObject,Bone):
    _bone = ArmatureObject.pose.bones[Bone]
    _obj = ArmatureObject
    return _obj.matrix_world * _bone.matrix

empty_ob = bpy.data.objects["Empty"]
armature_ob = bpy.data.objects["Armature"]

bone_name = bpy.context.active_pose_bone.name

empty_ob.matrix_world = worldMatrix(armature_ob, bone_name )
$\endgroup$
  • $\begingroup$ can you add some explanation? Math formulas are only good for some people. $\endgroup$ – David Oct 28 '18 at 20:21
  • $\begingroup$ I have added explanations and hope these clarify the title . if you feel this is not sufficient please tell me. $\endgroup$ – MohammadHossein Jamshidi Oct 30 '18 at 14:00
  • $\begingroup$ excellent answer! Much better. well done. UVed. $\endgroup$ – David Oct 30 '18 at 17:42
1
$\begingroup$

I copied the following script from this answer. The related commented functions are:


# ported from blenkernel/intern/armature.c to python
# --------------------------------------------------------------------
def get_mat_offs(bone):        
    mat_offs = bone.matrix.to_4x4()
    mat_offs.translation = bone.head
    mat_offs.translation.y += bone.parent.length

    return mat_offs

def get_mat_rest(pose_bone, mat_pose_parent):
    bone = pose_bone.bone

    if pose_bone.parent:
        mat_offs = get_mat_offs(bone)

        # --------- rotscale       
        if (not bone.use_inherit_rotation and 
            not bone.use_inherit_scale):                
            mat_rotscale = bone.parent.matrix_local * mat_offs

        elif not bone.use_inherit_rotation:                        
            mat_size = Matrix.Identity(4)
            for i in range(3):
                mat_size[i][i] = mat_pose_parent.col[i].magnitude
            mat_rotscale = mat_size * bone.parent.matrix_local * mat_offs

        elif not bone.use_inherit_scale:
            mat_rotscale = mat_pose_parent.normalized() * mat_offs

        else:
            mat_rotscale = mat_pose_parent * mat_offs

        # --------- location     
        if not bone.use_local_location:
            mat_a = Matrix.Translation(
                mat_pose_parent * mat_offs.translation)

            mat_b = mat_pose_parent.copy()
            mat_b.translation = Vector()

            mat_loc = mat_a * mat_b

        elif (not bone.use_inherit_rotation or 
              not bone.use_inherit_scale):                  
            mat_loc = mat_pose_parent * mat_offs

        else:
            mat_loc = mat_rotscale.copy()

    else:    
        mat_rotscale = bone.matrix_local
        if not bone.use_local_location:
            mat_loc = Matrix.Translation(bone.matrix_local.translation)
        else:
            mat_loc = mat_rotscale.copy()      

    return mat_rotscale, mat_loc

def get_mat_pose(pose_bone, mat_pose_parent=None, mat_basis=None):
    if pose_bone.parent and not mat_pose_parent:
        mat_pose_parent = pose_bone.parent.matrix
    if not mat_basis:
        mat_basis = pose_bone.matrix_basis

    mat_rotscale, mat_loc = get_mat_rest(pose_bone, mat_pose_parent)    
    mat_pose = mat_rotscale * mat_basis
    mat_pose.translation = mat_loc * mat_basis.translation

    return mat_pose    
# --------------------------------------------------------------------
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.