basic animation
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index eec7a49a2a76c81f4aecc76dd8586eddbfb650b8..d43d4af0d1f236042b04388aa7feaefbf37d8d95 100644 (file)
@@ -47,7 +47,7 @@ class mdl_node(Structure):
                ("submesh_count",c_uint32),
                ("classtype",c_uint32),
                ("offset",c_uint32),
-               ("children",c_uint32),
+               ("parent",c_uint32),
                ("pstr_name",c_uint32)]
 
 class mdl_header(Structure):
@@ -70,13 +70,27 @@ class mdl_header(Structure):
                ("node_count",c_uint32),
                ("node_offset",c_uint32),
 
+               ("anim_count",c_uint32),
+               ("anim_offset",c_uint32),
+
                ("strings_offset",c_uint32),
                ("entdata_offset",c_uint32),
-
-               ("anim_count",c_uint32),
-               ("anim_offset",c_uint32)
+               ("animdata_offset",c_uint32)
                ]
 
+class mdl_animation(Structure):
+   _pack_ = 1
+   _fields_ = [("pstr_name",c_uint32),
+               ("length",c_uint32),
+               ("rate",c_float),
+               ("offset",c_uint32)]
+
+class mdl_keyframe(Structure):
+   _pack_ = 1
+   _fields_ = [("co",c_float*3),
+               ("q",c_float*4),
+               ("s",c_float*3)]
+
 # Entity types
 # ==========================================
 
@@ -128,7 +142,8 @@ class classtype_skin(Structure):
 
 class classtype_skeleton(Structure):
    _pack_ = 1
-   _fields_ = [("anim_start",c_uint32),
+   _fields_ = [("channels",c_uint32),
+               ("anim_start",c_uint32),
                ("anim_count",c_uint32)]
 
 class classtype_bone(Structure):
@@ -165,6 +180,10 @@ def write_model(collection_name):
    entdata_buffer = []
    entdata_length = 0
 
+   anim_buffer = []
+   animdata_length = 0
+   animdata_buffer = []
+
    def emplace_string( s ):
       nonlocal string_cache, strings_buffer
 
@@ -230,7 +249,13 @@ def write_model(collection_name):
       return uid
 
    print( "  creating scene graph" )
-   graph = {"obj": None, "depth": 0, "children": [], "uid": _uid()}
+   graph = {}
+   graph["obj"] = None
+   graph["depth"] = 0
+   graph["children"] = []
+   graph["uid"] = _uid()
+   graph["parent"] = None
+
    graph_lookup = {} # object can lookup its graph def here
 
    for obj in collection.all_objects:
@@ -238,7 +263,12 @@ def write_model(collection_name):
 
          def _extend( p, n, d ):
             uid = _uid()
-            tree = {"obj":n, "depth": d, "children":[], "uid": uid}
+            tree = {}
+            tree["uid"] = uid
+            tree["children"] = []
+            tree["depth"] = d
+            tree["obj"] = n
+            tree["parent"] = p
             n.cv_data.uid = uid
 
             if n.type == 'ARMATURE':
@@ -247,16 +277,22 @@ def write_model(collection_name):
                def _extendb( p, n, d ):
                   nonlocal tree
 
-                  btree = {"bone":n, "depth": d, "children":[], "uid": _uid()}
+                  btree = {}
+                  btree["bone"] = n
+                  btree["uid"] = _uid()
+                  btree["children"] = []
+                  btree["depth"] = d
+                  btree["parent"] = p
+
+                  if n.use_deform:
+                     tree["bones"] += [n.name]
+
                   for c in n.children:
                      _extendb( btree, c, d+1 )
 
                   btree['deform'] = n.use_deform
                   p['children'] += [btree]
 
-                  if n.use_deform:
-                     tree["bones"] += [n.name]
-
                for b in n.data.bones:
                   if not b.parent:
                      _extendb( tree, b, d+1 )
@@ -277,7 +313,7 @@ def write_model(collection_name):
 
    it = _graph_iter(graph)
 
-   root.children = len(graph['children'])
+   root.parent = 0xffffffff
 
    # Compile
    # ==============================================
@@ -309,9 +345,9 @@ def write_model(collection_name):
       node.q[3] =  quat[0]
       
       if objt == 'BONE':
-         node.s[0] =  obj.tail[0]
-         node.s[1] =  obj.tail[2]
-         node.s[2] = -obj.tail[1]
+         node.s[0] =  obj.tail_local[0] - node.co[0]
+         node.s[1] =  obj.tail_local[2] - node.co[1]
+         node.s[2] = -obj.tail_local[1] - node.co[2]
       else:
          node.s[0] = obj.scale[0]
          node.s[1] = obj.scale[2]
@@ -319,6 +355,9 @@ def write_model(collection_name):
 
       node.pstr_name = emplace_string( obj.name )
 
+      if node_def["parent"]:
+         node.parent = node_def["parent"]["uid"]
+
       if objt == 'BONE':
          classtype = 'k_classtype_bone'
       elif objt == 'ARMATURE':
@@ -521,11 +560,13 @@ def write_model(collection_name):
       s001 = F" L {obj.name}"
       s002 = s000+s001
       s003 = F"{disptype}"
-      s004 = ""
+      s004 = F"{node.parent: 3}"
+      s005 = ""
+
       if classtype == 'k_classtype_skin':
-         s004 = F"-> {armature_def['obj'].cv_data.uid}"
+         s005 = F" [armature -> {armature_def['obj'].cv_data.uid}]"
 
-      scmp = F"{s002:<32} {s003:<16} {s004}"
+      scmp = F"{s002:<32} {s003:<16} {s004} {s005}"
       print( scmp )
       
       if classtype == 'k_classtype_INSTANCE' or \
@@ -549,10 +590,88 @@ def write_model(collection_name):
       elif classtype == 'k_classtype_skeleton':
          node.classtype = 11
          entdata_length += sizeof( classtype_skeleton )
-
          skeleton = classtype_skeleton()
-         skeleton.anim_start = 0
-         skeleton.anim_count = 0
+
+         armature_def = graph_lookup[obj]
+         armature = obj
+         bones = armature_def['bones']
+         print( bones )
+         skeleton.channels = len(bones)
+         
+         if armature.animation_data:
+            previous_frame = bpy.context.scene.frame_current
+            previous_action = armature.animation_data.action
+
+            skeleton.anim_start = len(anim_buffer)
+            skeleton.anim_count = 0
+
+            for NLALayer in obj.animation_data.nla_tracks:
+               for NLAStrip in NLALayer.strips:
+                  # Use action
+                  for a in bpy.data.actions:
+                     if a.name == NLAStrip.name:
+                        armature.animation_data.action = a
+                        break
+
+                  anim_start = int(NLAStrip.action_frame_start)
+                  anim_end   = int(NLAStrip.action_frame_end)
+
+                  # export strips
+                  anim = mdl_animation()
+                  anim.pstr_name = emplace_string( NLAStrip.action.name )
+                  anim.rate = 30.0
+                  anim.offset = animdata_length
+                  anim.length = anim_end-anim_start
+                  
+                  # Export the fucking keyframes
+                  for frame in range(anim_start,anim_end):
+                     bpy.context.scene.frame_set(frame)
+                     
+                     for bone_name in bones:
+                        for pb in armature.pose.bones:
+                           if pb.name == bone_name:
+                              rb = armature.data.bones[ bone_name ]
+
+                              loc, rot, sca = pb.matrix_basis.decompose()
+
+                              # local position
+                              vp = rb.matrix @ loc
+                              final_pos = Vector(( vp[0], vp[2], -vp[1] ))
+
+                              # rotation
+                              lc_m = pb.matrix_channel.to_3x3()
+                              if pb.parent is not None:
+                                 smtx = pb.parent.matrix_channel.to_3x3()
+                                 lc_m = smtx.inverted() @ lc_m
+                              rq = lc_m.to_quaternion()
+
+                              kf = mdl_keyframe()
+                              kf.co[0] =  loc[0]
+                              kf.co[1] =  loc[2]
+                              kf.co[2] = -loc[1]
+
+                              kf.q[0] =  rq[1]
+                              kf.q[1] =  rq[3]
+                              kf.q[2] = -rq[2]
+                              kf.q[3] =  rq[0]
+                              
+                              # scale
+                              kf.s[0] = sca[0]
+                              kf.s[1] = sca[2]
+                              kf.s[2] = sca[1]
+
+                              animdata_buffer += [kf]
+                              animdata_length += sizeof(mdl_keyframe)
+                              break
+
+                  anim_buffer += [anim]
+                  skeleton.anim_count += 1
+
+                  s000 = F"  [{uid: 3}/{header.node_count-1}]" + " |"*(depth-1)
+                  print( F"{s000} | *anim: {NLAStrip.action.name}" )
+            
+            bpy.context.scene.frame_set( previous_frame )
+            armature.animation_data.action = previous_action
 
          entdata_buffer += [skeleton]
 
@@ -669,6 +788,8 @@ def write_model(collection_name):
 
    # Write data arrays
    #
+   header.anim_count = len(anim_buffer)
+
    print( "Writing data" )
    fpos = sizeof(header)
 
@@ -684,6 +805,10 @@ def write_model(collection_name):
    header.material_offset = fpos
    fpos += sizeof(mdl_material)*header.material_count
 
+   print( F"Animation count: {header.anim_count}" )
+   header.anim_offset = fpos
+   fpos += sizeof(mdl_animation)*header.anim_count
+
    print( F"Entdata length: {entdata_length}" )
    header.entdata_offset = fpos
    fpos += entdata_length
@@ -695,6 +820,10 @@ def write_model(collection_name):
    print( F"Indice count: {header.indice_count}" )
    header.indice_offset = fpos
    fpos += sizeof(c_uint32)*header.indice_count
+
+   print( F"Keyframe count: {animdata_length}" )
+   header.animdata_offset = fpos
+   fpos += animdata_length
    
    print( F"Strings length: {len(strings_buffer)}" )
    header.strings_offset = fpos
@@ -713,12 +842,17 @@ def write_model(collection_name):
       fp.write( bytearray(sm) )
    for mat in material_buffer:
       fp.write( bytearray(mat) )
+   for a in anim_buffer:
+      fp.write( bytearray(a) )
    for ed in entdata_buffer:
       fp.write( bytearray(ed) )
    for v in vertex_buffer:
       fp.write( bytearray(v) )
    for i in indice_buffer:
       fp.write( bytearray(i) )
+   for kf in animdata_buffer:
+      fp.write( bytearray(kf) )
+
    fp.write( strings_buffer )
    fp.close()