From: hgn Date: Tue, 6 Sep 2022 22:56:13 +0000 (+0100) Subject: basic animation X-Git-Url: https://skaterift.com/git/?a=commitdiff_plain;h=1361a6d6ffda17feca6395beccf269763d3a76fa;p=carveJwlIkooP6JGAAIwe30JlM.git basic animation --- diff --git a/anim_test.h b/anim_test.h new file mode 100644 index 0000000..5ece5f5 --- /dev/null +++ b/anim_test.h @@ -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 */ diff --git a/blender_export.py b/blender_export.py index eec7a49..d43d4af 100644 --- a/blender_export.py +++ b/blender_export.py @@ -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 8c1ddef..6e94231 100644 --- 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 21a4e2f..5091301 100644 --- 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]; diff --git a/models_src/alter.mdl b/models_src/alter.mdl index 3bd08c4..f782230 100644 Binary files a/models_src/alter.mdl and b/models_src/alter.mdl differ diff --git a/models_src/ch_default.mdl b/models_src/ch_default.mdl index dc77e14..0d66961 100644 Binary files a/models_src/ch_default.mdl and b/models_src/ch_default.mdl differ diff --git a/models_src/ch_empty.mdl b/models_src/ch_empty.mdl index d7c1e56..6448b33 100644 Binary files a/models_src/ch_empty.mdl and b/models_src/ch_empty.mdl differ diff --git a/models_src/ch_mike.mdl b/models_src/ch_mike.mdl index a8ccbe1..6296030 100644 Binary files a/models_src/ch_mike.mdl and b/models_src/ch_mike.mdl differ diff --git a/models_src/ch_new.mdl b/models_src/ch_new.mdl index 4485a4f..b0b82a0 100644 Binary files a/models_src/ch_new.mdl and b/models_src/ch_new.mdl differ diff --git a/models_src/ch_outlaw.mdl b/models_src/ch_outlaw.mdl index 53aa669..25d55ad 100644 Binary files a/models_src/ch_outlaw.mdl and b/models_src/ch_outlaw.mdl differ diff --git a/models_src/epic_scene.mdl b/models_src/epic_scene.mdl index d20c9fb..858fe3d 100644 Binary files a/models_src/epic_scene.mdl and b/models_src/epic_scene.mdl differ diff --git a/models_src/mp_dev.mdl b/models_src/mp_dev.mdl index 3f57971..8b9200b 100644 Binary files a/models_src/mp_dev.mdl and b/models_src/mp_dev.mdl differ diff --git a/models_src/rs_cars.mdl b/models_src/rs_cars.mdl index 3894c23..6e75103 100644 Binary files a/models_src/rs_cars.mdl and b/models_src/rs_cars.mdl differ diff --git a/models_src/rs_chicken.mdl b/models_src/rs_chicken.mdl index 6784fb1..73c6686 100644 Binary files a/models_src/rs_chicken.mdl and b/models_src/rs_chicken.mdl differ diff --git a/models_src/rs_foliage.mdl b/models_src/rs_foliage.mdl index b2903d4..bac6866 100644 Binary files a/models_src/rs_foliage.mdl and b/models_src/rs_foliage.mdl differ diff --git a/models_src/rs_gate.mdl b/models_src/rs_gate.mdl index 939518e..9f4cddf 100644 Binary files a/models_src/rs_gate.mdl and b/models_src/rs_gate.mdl differ diff --git a/models_src/rs_scoretext.mdl b/models_src/rs_scoretext.mdl index ecc3ecb..359cc8a 100644 Binary files a/models_src/rs_scoretext.mdl and b/models_src/rs_scoretext.mdl differ diff --git a/models_src/rs_skydome.mdl b/models_src/rs_skydome.mdl index a817539..9cc16f0 100644 Binary files a/models_src/rs_skydome.mdl and b/models_src/rs_skydome.mdl differ diff --git a/models_src/rs_vig.mdl b/models_src/rs_vig.mdl index 91f4caa..26cdd0d 100644 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 index 0000000..4fb3098 --- /dev/null +++ b/skeleton.h @@ -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; ibone_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; ibone_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; ianim_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; inode_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; ianim_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; ibone_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 */