Bye bye bone extra matrices!

A place for to discuss the development of our tools for Blender.
Active developers: neomonkeus

Bye bye bone extra matrices!

Postby DinosaurManZT2 » Sat Jun 20, 2015 7:47 pm

Hi folks!
I created a blender plugin for Zoo Tycoon 2 BFB and BF files, which are essentially compressed NIF and KF files. The first version was heavily based on the Nif scripts to ensure compatibility between NIF and BFB files through blender, most importantly I also used the extra matrix system for bone correction.

Now I've ported the whole thing to the new blender API; it was more or less a complete rewrite to be exact. I looked through your code for inspiration and noticed a lot was still untouched from the old API.

Couple of days ago I started getting rid of the extra matrix system one by one. The bones are oriented correctly if each bone is rotated by 90° about its Z axis, so I use that method to generate the tail instead of averaging the child heads. (Note that is also looks better in most cases, especially if you have bone chains with additional bones towards the sides.) And that is the key to getting rid of the extra matrices: This fairly simple calculation can easily be reversed to get the matrix for model export or animation import. I haven't fixed animation export but animation import works perfectly.

Image

So yeah, if that rotation by 90° about a bone's Z axis works for all NIFs from all games, I think the extra matrix system could be removed.

If you're interested I can post the py code. ;)
DinosaurManZT2
 
Posts: 18
Joined: Mon Apr 09, 2012 7:53 am

Re: Bye bye bone extra matrices!

Postby Ghostwalker71 » Sun Jun 21, 2015 4:09 pm

Well I am always interested in seeing code. I have actually been working on a complete rewrite of the armature import and bone node import as well. I intend to overhaul how the mesh bones and the skeleton bones are displayed and interacted with in blender.
User avatar
Ghostwalker71
NifTools Developer
NifTools Developer
 
Posts: 886
Joined: Sun Jan 10, 2010 7:28 pm
Location: Texas, U aSs A

Re: Bye bye bone extra matrices!

Postby DinosaurManZT2 » Sun Jun 21, 2015 7:25 pm

Here's the current version, I've got animation export working as well:

https://drive.google.com/file/d/0B442Qu ... sp=sharing

Important functions for BFB import; the bone matrixes are stored in bone space in a BFB, just like in a NIF IIRC.

Code: Select all
def vec_roll_to_mat3(vec, roll):
   #from http://gamedev.stackexchange.com/questions/32529/calculating-the-correct-roll-from-a-bone-transform-matrix
   #from http://blenderartists.org/forum/showthread.php?260602-transform-matrix-to-bone-%28head-tail-roll%29-bug
   #not altered at all
   target = mathutils.Vector((0,1,0))
   nor = vec.normalized()
   axis = target.cross(nor)
   if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix
      axis.normalize()
      theta = target.angle(nor)
      bMatrix = mathutils.Matrix.Rotation(theta, 3, axis)
   else:
      updown = 1 if target.dot(nor) > 0 else -1
      bMatrix = mathutils.Matrix.Scale(updown, 3)
      bMatrix[2][2] = 1.0
   rMatrix = mathutils.Matrix.Rotation(roll, 3, nor)
   mat = rMatrix * bMatrix
   return mat

def mat3_to_vec_roll(mat):
   #from http://gamedev.stackexchange.com/questions/32529/calculating-the-correct-roll-from-a-bone-transform-matrix
   #from http://blenderartists.org/forum/showthread.php?260602-transform-matrix-to-bone-%28head-tail-roll%29-bug
   #this was edited so it rotates the axis and roll as if the matrix was rotated by 90° about the bone's Z axis
   #it would make more sense to keep this function in its original form and instead use a negative rotate_matrix_90Z() found in export
   #used to be: vec = mat.col[1]
   vec = mat.col[0]
   #used to be: vecmat = vec_roll_to_mat3(mat.col[1], 0)
   vecmat = vec_roll_to_mat3(mat.col[0], 0)
   vecmatinv = vecmat.inverted()
   rollmat = vecmatinv * mat
   roll = math.atan2(rollmat[0][2], rollmat[2][2])
   return vec, roll


def create_armature(bonesdict):

   global armature

   # Create armature and object
   bpy.ops.object.add(type = 'ARMATURE', enter_editmode = True, location = mathutils.Vector((0,0,0)))
   armature = bpy.context.object
   armature.show_x_ray = True
   armature.name = "DUMMY Scene Root"
   armData = armature.data
   armData.name = armature.name+'Amt'
   armData.show_axes = True
   armData.draw_type = 'STICK'
 
   bpy.ops.object.mode_set(mode = 'EDIT')   
   # Create bones
   for boneid in bonesdict:
      parentid,bonename,bonematrix = bonesdict[boneid]
      # create a new bone
      bone = armData.edit_bones.new(bonename)
      #iterate over all parents and multiply their matrices to create the matrix we need
      parentid = bonesdict[boneid][0]
      while parentid>0:
         parentmatrix = bonesdict[parentid][2]
         bonematrix = bonematrix*parentmatrix
         parentid = bonesdict[parentid][0]
      
      pos = bonematrix.transposed().to_translation()
      #this function is changed to output axis and roll as if the matrix was rotated by 90° about Z
      #it would probably make more sense to keep this function as it was and instead feed the matrix through a rotation like on export
      axis, roll = mat3_to_vec_roll(bonematrix.transposed().to_3x3())
      bone.head = pos
      bone.tail = pos + axis
      bone.roll = roll
      #set the parent
      parentid = bonesdict[boneid][0]
      if parentid>0:
         parentname = bonesdict[parentid][1]
         parentbone = armData.edit_bones[parentname]
         bone.parent = parentbone
   #fix the bone length
   for bone in armData.edit_bones:
      #don't change Bip01
      if bone.parent:
         childheads = mathutils.Vector()
         children = 0
         for child in armData.edit_bones:
            if child.parent == bone:
               childheads+= child.head
               children+= 1
         #nub node: no children, end of a chain
         if children == 0:
            bonelength = 0.5
         else:
            bonelength = (bone.head-childheads/children).length
            if bonelength == 0:
               bonelength = 0.25
         bone.length = bonelength
   bpy.ops.object.mode_set(mode = 'OBJECT')
   return armature


Then on BFB export, the rotation we made on import is simply countered to retrieve the original matrix. As mentioned in the code, the import should also be done with the rotate matrix function, just to keep it coherent.

Code: Select all

def rotate_matrix_90Z(m):
   #we want to get a rotation about the matrix' local Z axis, by 90 °
   #this affects both the axis and the bone's roll
   n = m.to_3x3()
   #when transposed from blender, use row
   #if working with raw blender data use col
   local_z = n[2]
   r = mathutils.Matrix.Rotation(math.radians(-90.0),3,local_z)
   n *= r
   n.resize_4x4()
   #add the translation components back in
   #test if it could be also done completely as a 4x4 matrix, or if that affects the tranlations
   n.col[3] = m.col[3]
   n[3] = m[3]
   return n

def get_rest_matrix(bone):
   #multiply with inverse parent matrix
   if bone.parent:
      return rotate_matrix_90Z(bone.matrix_local.transposed()) * rotate_matrix_90Z(bone.parent.matrix_local.transposed()).inverted()
   #else it has no parent
   return rotate_matrix_90Z(bone.matrix_local.transposed())


Animation import and export also use get_rest_matrix(bone) to get the rest matrix. The quats and translations are stored just like in NIFs.
A few calculations are done to make them work in blender, making the keys relative to the bones or whatever. That was lifted from the original NIF scripts, but any trace of extra matrices is now gone.
The real trick is a simple swizzle - on import quat.wxyz becomes quat.w-yxz and loc.xyz becomes loc.y-xz, and vice versa on export. That swizzling equals the ominous 90° rotation again and makes sure the rotations are aligned properly to the fixed roll and axis of the bones.
The code is much more simple than it used to be when I used extra matrices!

So with that technique, you have good looking skeletons AND fully functional animation support WITHOUT the need for correction matrices. It's so much easier to create custom skeletons now, because there's no more need to worry about the extra matrices, as your blender bones directly determine your end result matrices.
DinosaurManZT2
 
Posts: 18
Joined: Mon Apr 09, 2012 7:53 am

Re: Bye bye bone extra matrices!

Postby DinosaurManZT2 » Sun Jun 21, 2015 7:34 pm

Argh just exceeded the character limit lol

One more thing, what I always found was lacking in the nif scripts is proper collision visualization that can be edited visually. I've also got that working in my scripts.

Finally, here are some sample vanilla BFB models and BF animations:
https://drive.google.com/file/d/0B442Qu ... sp=sharing
DinosaurManZT2
 
Posts: 18
Joined: Mon Apr 09, 2012 7:53 am

Re: Bye bye bone extra matrices!

Postby bitwarp » Sun Oct 09, 2016 2:51 am

DinosaurManZT2 wrote:Here's the current version, I've got animation export working as well:

https://drive.google.com/file/d/0B442Qu ... sp=sharing

Important functions for BFB import; the bone matrixes are stored in bone space in a BFB, just like in a NIF IIRC.


Image

Do I need a specific version of Python or another dependency?

I'm already able to use Nifskope to extract the Zoo Tycoon 2 animals (Z2T really a ZIP file) from the NIF file, they have their skin and bones (I'm not sure if the bones are OK).

I just need something that allows the extraction of the animated 3D assets so I can export to FBX, Blender or DAE so I can finish my graduation project as fast as possible. The best I've found so far are Z2T files.

If I can't get the animations. How much time would it take for me to get the animals moving ? (I don't know about 3D animation and I don't know if the bones are good after extracting from the NIF file that was inside the Z2T[ZIP file]).
bitwarp
 
Posts: 1
Joined: Sat Oct 08, 2016 11:30 pm


Return to Blender Plug-in Development

Who is online

Users browsing this forum: No registered users and 1 guest