basic animation
authorhgn <hgodden00@gmail.com>
Tue, 6 Sep 2022 22:56:13 +0000 (23:56 +0100)
committerhgn <hgodden00@gmail.com>
Tue, 6 Sep 2022 22:56:13 +0000 (23:56 +0100)
20 files changed:
anim_test.h [new file with mode: 0644]
blender_export.py
main.c
model.h
models_src/alter.mdl
models_src/ch_default.mdl
models_src/ch_empty.mdl
models_src/ch_mike.mdl
models_src/ch_new.mdl
models_src/ch_outlaw.mdl
models_src/epic_scene.mdl
models_src/mp_dev.mdl
models_src/rs_cars.mdl
models_src/rs_chicken.mdl
models_src/rs_foliage.mdl
models_src/rs_gate.mdl
models_src/rs_scoretext.mdl
models_src/rs_skydome.mdl
models_src/rs_vig.mdl
skeleton.h [new file with mode: 0644]

diff --git a/anim_test.h b/anim_test.h
new file mode 100644 (file)
index 0000000..5ece5f5
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef ANIM_TEST_H
+#define ANIM_TEST_H
+
+#include "player.h"
+#include "skeleton.h"
+
+static struct 
+{
+   struct skeleton skele;
+   struct skeleton_anim *yay;
+}
+animtest;
+
+static void anim_test_start(void)
+{
+   mdl_header *johannes = mdl_load( "models/ch_new.mdl" );
+   skeleton_setup( &animtest.skele, johannes );
+   animtest.yay = skeleton_get_anim( &animtest.skele, "yay" );
+
+   free( johannes );
+}
+
+static void anim_test_update(void)
+{
+   player_freecam();
+   player_camera_update();
+   
+   m4x3f transform;
+   m4x3_identity( transform );
+   skeleton_apply_frame( transform, &animtest.skele, animtest.yay, vg_time );
+
+   skeleton_debug( &animtest.skele );
+}
+
+static void anim_test_render(void)
+{
+   m4x4f world_4x4;
+   m4x3_expand( player.camera_inverse, world_4x4 );
+
+   gpipeline.fov = 60.0f;
+   m4x4_projection( vg_pv, gpipeline.fov, 
+         (float)vg_window_x / (float)vg_window_y, 
+         0.1f, 2100.0f );
+
+   m4x4_mul( vg_pv, world_4x4, vg_pv );
+   glEnable( GL_DEPTH_TEST );
+
+   glDisable( GL_DEPTH_TEST );
+   vg_lines_drawall( (float *)vg_pv );
+}
+
+#endif /* ANIM_TEST_H */
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()
 
diff --git a/main.c b/main.c
index 8c1ddef2811b537bcbec692426b676fa8f056ec7..6e94231edd9d0e4407ec530225170bafbf2e60cb 100644 (file)
--- a/main.c
+++ b/main.c
@@ -17,7 +17,7 @@ vg_tex2d tex_water = { .path = "textures/water.qoi" };
 static int debugview = 0;
 static int sv_debugcam = 0;
 static int lightedit = 0;
-static int sv_scene = 0;
+static int sv_scene = 2;
 
 /* Components */
 //#define SR_NETWORKED
@@ -47,6 +47,7 @@ static int sv_scene = 0;
 #include "shaders/unlit.h"
 
 #include "physics_test.h"
+#include "anim_test.h"
 
 void vg_register(void)
 {
@@ -194,11 +195,14 @@ void vg_start(void)
 
       network_init();
    }
-   else
+   else if( sv_scene == 1 )
    {
       physics_test_start();
    }
-
+   else if( sv_scene == 2 )
+   {
+      anim_test_start();
+   }
 }
 
 void vg_free(void)
@@ -230,6 +234,10 @@ void vg_update(void)
    {
       physics_test_update();
    }
+   else if( sv_scene == 2 )
+   {
+      anim_test_update();
+   }
 }
 
 static void vg_framebuffer_resize( int w, int h )
@@ -347,6 +355,10 @@ void vg_render(void)
    {
       physics_test_render();
    }
+   else if( sv_scene == 2 )
+   {
+      anim_test_render();
+   }
 #endif
 }
 
diff --git a/model.h b/model.h
index 21a4e2fe2e75a4a8917d4f6af09aee44574419f4..50913017fab68d1925f2d838668df961303cca76 100644 (file)
--- a/model.h
+++ b/model.h
@@ -10,6 +10,8 @@ typedef struct mdl_submesh mdl_submesh;
 typedef struct mdl_material mdl_material;
 typedef struct mdl_node mdl_node;
 typedef struct mdl_header mdl_header;
+typedef struct mdl_animation mdl_animation;
+typedef struct mdl_keyframe mdl_keyframe;
 
 #define MDL_SIZE_MAX          0x1000000
 #define MDL_VERT_MAX          1000000
@@ -59,10 +61,27 @@ struct mdl_node
        submesh_count,
        classtype,
        offset,
-       children,
+       parent,
        pstr_name;
 };
 
+struct mdl_keyframe
+{
+   v3f co;
+   v4f q;
+   v3f s;
+};
+
+struct mdl_animation
+{
+   u32 pstr_name,
+       length;
+
+   float rate;
+
+   u32 offset;
+};
+
 struct mdl_header
 {
    u32 identifier, version, file_length;
@@ -72,8 +91,8 @@ struct mdl_header
        submesh_count, submesh_offset,
        material_count, material_offset,
        node_count, node_offset,
-       strings_offset, entdata_offset,
-       anim_count, anim_offset;
+       anim_count, anim_offset,
+       strings_offset, entdata_offset, animdata_offset;
 };
 
 /* 
@@ -135,7 +154,8 @@ struct classtype_bone
 
 struct classtype_skeleton
 {
-   u32 anim_start,
+   u32 channels,
+       anim_start,
        anim_count;
 };
 
@@ -423,6 +443,11 @@ static mdl_material *mdl_material_from_id( mdl_header *mdl, u32 id )
    return ((mdl_material *)mdl_baseptr(mdl,mdl->material_offset)) + id;
 }
 
+static mdl_animation *mdl_animation_from_id( mdl_header *mdl, u32 id )
+{
+   return ((mdl_animation *)mdl_baseptr(mdl,mdl->anim_offset)) + id;
+}
+
 static void mdl_node_transform( mdl_node *pnode, m4x3f transform )
 {
    q_m3x3( pnode->q, transform );
@@ -470,6 +495,11 @@ static void *mdl_get_entdata( mdl_header *mdl, mdl_node *pnode )
    return mdl_baseptr( mdl, mdl->entdata_offset ) + pnode->offset;
 }
 
+static mdl_keyframe *mdl_get_animdata( mdl_header *mdl, mdl_animation *anim )
+{
+   return mdl_baseptr( mdl, mdl->animdata_offset ) + anim->offset;
+}
+
 static void mdl_link_materials( mdl_header *root, mdl_header *child )
 {
    u32 lookup[MDL_MATERIAL_MAX];
index 3bd08c4271632564200e19390d3f1be6c6bdfb81..f782230a888acbeb0184534006c85669a9b5aaa7 100644 (file)
Binary files a/models_src/alter.mdl and b/models_src/alter.mdl differ
index dc77e146238fe84529c7718b6f7f09ff7e510eba..0d669618e2f47c90a23dd521b6289b8f57c1c3ee 100644 (file)
Binary files a/models_src/ch_default.mdl and b/models_src/ch_default.mdl differ
index d7c1e56371f2fa77ccd276ca9f441c845d9041f7..6448b33c8cdcf5ac11f8b04b0a18f9956020525d 100644 (file)
Binary files a/models_src/ch_empty.mdl and b/models_src/ch_empty.mdl differ
index a8ccbe103415530779a90138e9bb99f7e7f385e2..6296030ba3e1462368c08a78540dea22b7d15d6b 100644 (file)
Binary files a/models_src/ch_mike.mdl and b/models_src/ch_mike.mdl differ
index 4485a4f76b8fe446f82d8c786cc47ad8b4472550..b0b82a0f6ff2f423bbdc2dd18a9791b42ec84c3f 100644 (file)
Binary files a/models_src/ch_new.mdl and b/models_src/ch_new.mdl differ
index 53aa669f06a1ccc8f39ccb9ae0085560a184e264..25d55ad1e43de973efce4d0c9315a9d4f8de2018 100644 (file)
Binary files a/models_src/ch_outlaw.mdl and b/models_src/ch_outlaw.mdl differ
index d20c9fbf3744b1fd38de3595c372bae88127ad71..858fe3d41a5d95f7424b1d985131f9e986ba7cf4 100644 (file)
Binary files a/models_src/epic_scene.mdl and b/models_src/epic_scene.mdl differ
index 3f57971736237e678541921f137bac76ef1fad78..8b9200ba8a951e2b702311dcdedeae21782b9992 100644 (file)
Binary files a/models_src/mp_dev.mdl and b/models_src/mp_dev.mdl differ
index 3894c230227414ea214b8598c122619864dad0d2..6e751036969aa62f70dac513a0020372f51e3c0a 100644 (file)
Binary files a/models_src/rs_cars.mdl and b/models_src/rs_cars.mdl differ
index 6784fb19ab1044fc7fa13f7a205835ed3aa9bfd8..73c66867e328244b3196ddd028af308f780d1896 100644 (file)
Binary files a/models_src/rs_chicken.mdl and b/models_src/rs_chicken.mdl differ
index b2903d46d176aa1fa0a220cd41b75abe4b73333a..bac68665acb0599c436cdac03b2e201a8a60aef2 100644 (file)
Binary files a/models_src/rs_foliage.mdl and b/models_src/rs_foliage.mdl differ
index 939518e7eaafe01b654612ff63b99054beb7edba..9f4cddfbf267a7997f97dd72b83329735b03870d 100644 (file)
Binary files a/models_src/rs_gate.mdl and b/models_src/rs_gate.mdl differ
index ecc3ecb317d7bbd3b324aab1358770046c90ba1e..359cc8a66f46cba31329305e4ec6e5de55d934c3 100644 (file)
Binary files a/models_src/rs_scoretext.mdl and b/models_src/rs_scoretext.mdl differ
index a817539dca0a63c8e019107ab696131b0c1fd273..9cc16f0671a8e01a1b55837586dc6e81768ae5bb 100644 (file)
Binary files a/models_src/rs_skydome.mdl and b/models_src/rs_skydome.mdl differ
index 91f4caae7a73e3f7846dd511d48af257930b200b..26cdd0dc0fba211c4b39bdfbcd24e0621375ea47 100644 (file)
Binary files a/models_src/rs_vig.mdl and b/models_src/rs_vig.mdl differ
diff --git a/skeleton.h b/skeleton.h
new file mode 100644 (file)
index 0000000..4fb3098
--- /dev/null
@@ -0,0 +1,195 @@
+#ifndef SKELETON_H
+#define SKELETON_H
+
+#include "model.h"
+
+struct skeleton
+{
+   struct skeleton_bone
+   {
+      v3f co, end;
+      u32 children; /* maybe remove */
+      u32 parent;
+      
+      mdl_keyframe kf;
+   }
+   *bones;
+   m4x3f *final_transforms;
+
+   struct skeleton_anim
+   {
+      float rate;
+      u32 length;
+      struct mdl_keyframe *anim_data;
+      char name[32];
+   }
+   *anims;
+
+   u32 bone_count,
+       anim_count;
+};
+
+static void skeleton_apply_frame( m4x3f transform,
+                                  struct skeleton *skele,
+                                  struct skeleton_anim *anim, 
+                                  float time )
+{
+   u32 frame = time*anim->rate;
+   frame = frame % anim->length;
+
+   mdl_keyframe *base = anim->anim_data + (skele->bone_count-1)*frame;
+   m4x3_copy( transform, skele->final_transforms[0] );
+
+   for( int i=1; i<skele->bone_count; i++ )
+   {
+      struct skeleton_bone *sb = &skele->bones[i];
+
+      /* process pose */
+      m4x3f posemtx;
+
+      v3f temp_delta;
+      v3_sub( skele->bones[i].co, skele->bones[sb->parent].co, temp_delta );
+
+
+      /* pose matrix */
+      mdl_keyframe *kf = base+i-1;
+
+      q_m3x3( kf->q, posemtx );
+      v3_copy( kf->co, posemtx[3] );
+      v3_add( temp_delta, posemtx[3], posemtx[3] );
+
+      /* final matrix */
+      m4x3_mul( skele->final_transforms[ sb->parent ], posemtx, 
+                        skele->final_transforms[i] );
+   }
+
+   /* armature space -> bone space matrix ( for verts ) */
+   for( int i=1; i<skele->bone_count; i++ )
+   {
+      m4x3f abmtx;
+      m3x3_identity( abmtx );
+      v3_negate( skele->bones[i].co, abmtx[3] );
+      m4x3_mul( skele->final_transforms[i], abmtx,
+                skele->final_transforms[i] );
+   }
+}
+
+static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele,
+                                                const char *name )
+{
+   for( int i=0; i<skele->anim_count; i++ )
+   {
+      struct skeleton_anim *anim = &skele->anims[i];
+
+      if( !strcmp( anim->name, name ) )
+         return anim;
+   }
+
+   return NULL;
+}
+
+/* Setup an animated skeleton from model */
+static int skeleton_setup( struct skeleton *skele, mdl_header *mdl )
+{
+   u32 bone_count = 1, skeleton_root = 0;
+   skele->bone_count = 0;
+   skele->bones = NULL;
+   skele->final_transforms = NULL;
+   skele->anims = NULL;
+
+   struct classtype_skeleton *inf = NULL;
+
+   for( u32 i=0; i<mdl->node_count; i++ )
+   {
+      mdl_node *pnode = mdl_node_from_id( mdl, i );
+
+      if( pnode->classtype == k_classtype_skeleton )
+      {
+         inf = mdl_get_entdata( mdl, pnode );
+         if( skele->bone_count )
+         {
+            vg_error( "Multiple skeletons in model file\n" );
+            free( skele->bones );
+            return 0;
+         }
+         
+         skele->bone_count = inf->channels;
+         skele->bones = malloc(sizeof(struct skeleton_bone)*skele->bone_count);
+         skeleton_root = i;
+      }
+      else if( skele->bone_count )
+      {
+         if( pnode->classtype == k_classtype_bone )
+         {
+            struct skeleton_bone *sb = &skele->bones[bone_count ++];
+            v3_copy( pnode->co, sb->co );
+            v3_copy( pnode->s, sb->end );
+            sb->parent = pnode->parent-skeleton_root;
+         }
+         else
+         {
+            break;
+         }
+      }
+   }
+
+   if( !inf )
+   {
+      vg_error( "No skeleton in model\n" );
+      return 0;
+   }
+
+   if( bone_count != skele->bone_count )
+   {
+      vg_error( "Loaded %u bones out of %u\n", bone_count, skele->bone_count );
+      return 0;
+   }
+
+   /* fill in implicit root bone */
+   v3_zero( skele->bones[0].co );
+   v3_copy( (v3f){0.0f,1.0f,0.0f}, skele->bones[0].end );
+   skele->bones[0].parent = 0xffffffff;
+   
+   skele->final_transforms = malloc( sizeof(m4x3f) * skele->bone_count );
+   skele->anim_count = inf->anim_count;
+   skele->anims = malloc( sizeof(struct skeleton_anim) * inf->anim_count);
+   
+   for( int i=0; i<inf->anim_count; i++ )
+   {
+      mdl_animation *anim = 
+         mdl_animation_from_id( mdl, inf->anim_start+i );
+
+      skele->anims[i].rate = anim->rate;
+      skele->anims[i].length = anim->length;
+      strncpy( skele->anims[i].name, mdl_pstr(mdl, anim->pstr_name), 32 );
+
+      u32 total_keyframes = (skele->bone_count-1)*anim->length;
+      size_t block_size = sizeof(mdl_keyframe) * total_keyframes;
+      mdl_keyframe *dst = malloc( block_size );
+
+      skele->anims[i].anim_data = dst;
+      memcpy( dst, mdl_get_animdata( mdl, anim ), block_size );
+   }
+
+   vg_success( "Loaded skeleton with %u bones\n", skele->bone_count );
+   return 1;
+}
+
+static void skeleton_debug( struct skeleton *skele )
+{
+   for( int i=0; i<skele->bone_count; i ++ )
+   {
+      struct skeleton_bone *sb = &skele->bones[i];
+
+      v3f p0, p1;
+      v3_copy( sb->co, p0 );
+      v3_add( p0, sb->end, p1 );
+      vg_line( p0, p1, 0xffffffff );
+
+      m4x3_mulv( skele->final_transforms[i], p0, p0 );
+      m4x3_mulv( skele->final_transforms[i], p1, p1 );
+      vg_line( p0, p1, 0xff0000ff );
+   }
+}
+
+#endif /* SKELETON_H */