Sunday 26 December 2010

Blender Armatures and Exporting


One of the things i have spent many time trying to understand is the Blender animation data for the armatures. Since the quaternions and translation vectors have their own special space, which depends on the bone orientation and roll vector as shown in how armatures work, it isn't always easy to translate the animations to other formats like collada, fbx, bvh or to export the data for real-time applications.

The first thing to take in consideration is the matrix theoretical layout used to describe armature chain, like OpenGL, Blender uses the translation vector in a column, which may create some confusion. One typical example of this issue is the OpenGL (2) and DirectX (1) shaders, at machine level, they are done in the same way (see the gluProject source from Mesa), but from the programmers high level, the syntax is quite the opposite for the following cases :
out.pos = mul(float4(in.vertex,1.0), WorldViewProj ) (1)
gl_Position = WorldViewProj  * vec4(vertex, 1.0) (2)


Keeping this in mind we rewrite the blender armature expression the way i like (3).
b.matrix = IK(b) * Constrains(b) * actions(b) * bonemat(b) * head(b) * tail(b-1) * b.parent.matrix (3)

Ignoring the IK and Constrains we have something similar to what i use for my armatures (4), but still, the action data (quaternions and translations) are affected by the bonemat matrix, which disables the direct use of the animation data from blender.
bone.matrix = Scale(bone.scale) * Rotate(bone.quaternion) * Translate(bone.head+bone.translation) *Translate(bone.parent.tail-bone.parent.head)*bone.parent.matrix (4)


To fix this issue, i found this expression to be the solution to export Blender animation data for my applications and the bXporter API (5).

matArmatureSpace = b.matrix['ARMATURESPACE']
matInvArmatureSpace = Matrix(matArmatureSpace).invert()

rotFixed = (matInvArmatureSpace.rotationPart()* graphQuaternion.toMatrix()*
matArmatureSpace.rotationPart()).toQuat()

posFixed = (matInvArmatureSpace*
TranslationMatrix(graphPosition)*
matArmatureSpace.rotationPart()).translationPart() (5)

This peculiar transformation applies the transformation of the bone rest pose in armature space to the current action data, this way we get the bone animated in armature space, then by applying the inverse bone rest pose matrix, we get the the rotation and translation relative to bone space and independent of the bonemat matrix, enabling the use of the expression (4).

Currently i have even fixed the issue with the animation beziers with another solution which doesn't use Cardano solver like in Blender or ILM ones, which consists in recalculate the bezier coefficients, but that will be another blogpost :)