From: hgn Date: Thu, 8 Sep 2022 15:09:43 +0000 (+0100) Subject: much better implicit IK solver X-Git-Url: https://skaterift.com/git/?a=commitdiff_plain;h=3d5597be2fd4b5d9ea3bf8863e15cc9bc8123755;p=carveJwlIkooP6JGAAIwe30JlM.git much better implicit IK solver --- diff --git a/anim_test.h b/anim_test.h index 5ece5f5..c2f3eae 100644 --- a/anim_test.h +++ b/anim_test.h @@ -3,11 +3,14 @@ #include "player.h" #include "skeleton.h" +#include "shaders/viewchar.h" static struct { struct skeleton skele; struct skeleton_anim *yay; + + glmesh mesh; } animtest; @@ -15,7 +18,8 @@ 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" ); + animtest.yay = skeleton_get_anim( &animtest.skele, "pose_stand" ); + mdl_unpack_glmesh( johannes, &animtest.mesh ); free( johannes ); } @@ -27,12 +31,18 @@ static void anim_test_update(void) m4x3f transform; m4x3_identity( transform ); + + v4f qt; + q_axis_angle( qt, (v3f){0.0f,1.0f,0.0f}, vg_time*1.2f ); + q_m3x3( qt, transform ); + skeleton_apply_frame( transform, &animtest.skele, animtest.yay, vg_time ); + skeleton_apply_ik_pass( &animtest.skele ); skeleton_debug( &animtest.skele ); } -static void anim_test_render(void) +static void anim_test_render( vg_tex2d *tex ) { m4x4f world_4x4; m4x3_expand( player.camera_inverse, world_4x4 ); @@ -45,6 +55,18 @@ static void anim_test_render(void) m4x4_mul( vg_pv, world_4x4, vg_pv ); glEnable( GL_DEPTH_TEST ); + shader_viewchar_use(); + vg_tex2d_bind( tex, 0 ); + shader_viewchar_uTexMain( 0 ); + shader_viewchar_uPv( vg_pv ); + glUniformMatrix4x3fv( _uniform_viewchar_uTransforms, + animtest.skele.bone_count, + 0, + (float *)animtest.skele.final_transforms ); + + mesh_bind( &animtest.mesh ); + mesh_draw( &animtest.mesh ); + glDisable( GL_DEPTH_TEST ); vg_lines_drawall( (float *)vg_pv ); } diff --git a/blender_export.py b/blender_export.py index d43d4af..3f2dd79 100644 --- a/blender_export.py +++ b/blender_export.py @@ -143,6 +143,7 @@ class classtype_skin(Structure): class classtype_skeleton(Structure): _pack_ = 1 _fields_ = [("channels",c_uint32), + ("ik_count",c_uint32), ("anim_start",c_uint32), ("anim_count",c_uint32)] @@ -150,6 +151,12 @@ class classtype_bone(Structure): _pack_ = 1 _fields_ = [("deform",c_uint32)] +class classtype_ik_bone(Structure): + _pack_ = 1 + _fields_ = [("deform",c_uint32), + ("target",c_uint32), + ("pole",c_uint32)] + # Exporter # ============================================================================== @@ -273,6 +280,7 @@ def write_model(collection_name): if n.type == 'ARMATURE': tree["bones"] = [None] # None is the root transform + tree["ik_count"] = 0 def _extendb( p, n, d ): nonlocal tree @@ -283,13 +291,17 @@ def write_model(collection_name): btree["children"] = [] btree["depth"] = d btree["parent"] = p - - if n.use_deform: - tree["bones"] += [n.name] + tree["bones"] += [n.name] for c in n.children: _extendb( btree, c, d+1 ) + for c in tree['obj'].pose.bones[n.name].constraints: + if c.type == 'IK': + btree["target"] = c.subtarget + btree["pole"] = c.pole_subtarget + tree["ik_count"] += 1 + btree['deform'] = n.use_deform p['children'] += [btree] @@ -359,7 +371,10 @@ def write_model(collection_name): node.parent = node_def["parent"]["uid"] if objt == 'BONE': - classtype = 'k_classtype_bone' + if 'target' in node_def: + classtype = 'k_classtype_ik_bone' + else: + classtype = 'k_classtype_bone' elif objt == 'ARMATURE': classtype = 'k_classtype_skeleton' else: @@ -384,6 +399,7 @@ def write_model(collection_name): if mod.type == 'ARMATURE': classtype = 'k_classtype_skin' armature_def = graph_lookup[mod.object] + armature_def['obj'].data.pose_position = 'REST' if can_use_cache and obj.data.name in mesh_cache: ref = mesh_cache[obj.data.name] @@ -564,9 +580,10 @@ def write_model(collection_name): s005 = "" if classtype == 'k_classtype_skin': + armature_def['obj'].data.pose_position = 'POSE' s005 = F" [armature -> {armature_def['obj'].cv_data.uid}]" - scmp = F"{s002:<32} {s003:<16} {s004} {s005}" + scmp = F"{s002:<32} {s003:<22} {s004} {s005}" print( scmp ) if classtype == 'k_classtype_INSTANCE' or \ @@ -595,8 +612,8 @@ def write_model(collection_name): armature_def = graph_lookup[obj] armature = obj bones = armature_def['bones'] - print( bones ) skeleton.channels = len(bones) + skeleton.ik_count = armature_def["ik_count"] if armature.animation_data: previous_frame = bpy.context.scene.frame_current @@ -646,9 +663,9 @@ def write_model(collection_name): rq = lc_m.to_quaternion() kf = mdl_keyframe() - kf.co[0] = loc[0] - kf.co[1] = loc[2] - kf.co[2] = -loc[1] + kf.co[0] = final_pos[0] + kf.co[1] = final_pos[1] + kf.co[2] = final_pos[2] kf.q[0] = rq[1] kf.q[1] = rq[3] @@ -680,9 +697,20 @@ def write_model(collection_name): entdata_length += sizeof( classtype_bone ) bone = classtype_bone() - bone.use_deform = node_def['deform'] + bone.deform = node_def['deform'] entdata_buffer += [bone] + elif classtype == 'k_classtype_ik_bone': + node.classtype = 13 + entdata_length += sizeof( classtype_ik_bone ) + + ikbone = classtype_ik_bone() + ikbone.target = armature_def['bones'].index( node_def['target'] ) + ikbone.pole = armature_def['bones'].index( node_def['pole'] ) + ikbone.deform = node_def['deform'] + + entdata_buffer += [ikbone] + elif classtype == 'k_classtype_gate': node.classtype = 1 entdata_length += sizeof( classtype_gate ) diff --git a/common.h b/common.h index afa32fd..efe9a88 100644 --- a/common.h +++ b/common.h @@ -22,7 +22,9 @@ enum classtype k_classtype_route_node = 8, k_classtype_route = 9, k_classtype_bone = 10, - k_classtype_skeleton = 11 + k_classtype_skeleton = 11, + k_classtype_skin = 12, + k_classtype_ik_bone = 13 }; /* TODO: he needs a home somewhere */ diff --git a/main.c b/main.c index 6e94231..2ae90c8 100644 --- a/main.c +++ b/main.c @@ -55,6 +55,7 @@ void vg_register(void) shader_standard_register(); shader_vblend_register(); shader_unlit_register(); + shader_viewchar_register(); world_register(); character_register(); @@ -357,7 +358,7 @@ void vg_render(void) } else if( sv_scene == 2 ) { - anim_test_render(); + anim_test_render( &tex_pallet ); } #endif } diff --git a/model.h b/model.h index 5091301..54874bd 100644 --- a/model.h +++ b/model.h @@ -152,9 +152,17 @@ struct classtype_bone u32 deform; }; +struct classtype_ik_bone +{ + u32 deform, + target, + pole; +}; + struct classtype_skeleton { u32 channels, + ik_count, anim_start, anim_count; }; diff --git a/models_src/ch_new.mdl b/models_src/ch_new.mdl index b0b82a0..d4cf5c7 100644 Binary files a/models_src/ch_new.mdl and b/models_src/ch_new.mdl differ diff --git a/shaders.sh b/shaders.sh index b02bacc..e958679 100755 --- a/shaders.sh +++ b/shaders.sh @@ -27,6 +27,7 @@ shader gpos standard.vs gpos.fs shader route standard.vs route.fs shader scoretext scoretext.vs vblend.fs shader routeui routeui.vs routeui.fs +shader viewchar standard_skinned.vs viewchar.fs cd shaders ../bin/linux/tools/shader $target_shaders diff --git a/shaders/standard_skinned.vs b/shaders/standard_skinned.vs new file mode 100644 index 0000000..5c959a0 --- /dev/null +++ b/shaders/standard_skinned.vs @@ -0,0 +1,31 @@ +#include "vertex_standard.glsl" + +uniform mat4 uPv; +uniform mat4x3 uTransforms[32]; + +out vec4 aColour; +out vec2 aUv; +out vec3 aNorm; +out vec3 aCo; +out vec3 aWorldCo; + +void main() +{ + vec4 co_local = vec4( a_co, 1.0 ); + vec3 co0 = uTransforms[ a_groups[0] ] * co_local; + vec3 co1 = uTransforms[ a_groups[1] ] * co_local; + vec3 co2 = uTransforms[ a_groups[2] ] * co_local; + vec3 n0 = mat3(uTransforms[ a_groups[0] ]) * a_norm; + vec3 n1 = mat3(uTransforms[ a_groups[1] ]) * a_norm; + vec3 n2 = mat3(uTransforms[ a_groups[2] ]) * a_norm; + + vec3 world_pos = co0*a_weights[0] + co1*a_weights[1] + co2*a_weights[2]; + vec3 world_normal = n0*a_weights[0] + n1*a_weights[1] + n2*a_weights[2]; + + gl_Position = uPv * vec4( world_pos, 1.0 ); + aColour = a_colour; + aUv = a_uv; + aNorm = world_normal; + aCo = a_co; + aWorldCo = world_pos; +} diff --git a/shaders/viewchar.fs b/shaders/viewchar.fs new file mode 100644 index 0000000..ffc969d --- /dev/null +++ b/shaders/viewchar.fs @@ -0,0 +1,28 @@ +out vec4 FragColor; + +uniform sampler2D uTexMain; + +in vec4 aColour; +in vec2 aUv; +in vec3 aNorm; +in vec3 aCo; +in vec3 aWorldCo; + +#include "common_world.glsl" + +void main() +{ + vec3 vfrag = texture( uTexMain, aUv ).rgb; + + // Lighting + //vec3 halfview = uCamera - aWorldCo; + //float fdist = length( halfview ); + //halfview /= fdist; + + //vfrag = do_light_diffuse( vfrag, aNorm ); + //vfrag = do_light_spec( vfrag, aNorm, halfview, 0.1 ); + //vfrag = do_light_shadowing( vfrag ); + //vfrag = apply_fog( vfrag, fdist ); + + FragColor = vec4(aNorm,1.0); +} diff --git a/shaders/viewchar.h b/shaders/viewchar.h new file mode 100644 index 0000000..23cb8b7 --- /dev/null +++ b/shaders/viewchar.h @@ -0,0 +1,207 @@ +#ifndef SHADER_viewchar_H +#define SHADER_viewchar_H +static void shader_viewchar_link(void); +static void shader_viewchar_register(void); +static struct vg_shader _shader_viewchar = { + .name = "viewchar", + .link = shader_viewchar_link, + .vs = +{ +.orig_file = "../../shaders/standard_skinned.vs", +.static_src = +"layout (location=0) in vec3 a_co;\n" +"layout (location=1) in vec3 a_norm;\n" +"layout (location=2) in vec2 a_uv;\n" +"layout (location=3) in vec4 a_colour;\n" +"layout (location=4) in vec4 a_weights;\n" +"layout (location=5) in ivec4 a_groups;\n" +"\n" +"#line 2 0 \n" +"\n" +"uniform mat4 uPv;\n" +"uniform mat4x3 uTransforms[32];\n" +"\n" +"out vec4 aColour;\n" +"out vec2 aUv;\n" +"out vec3 aNorm;\n" +"out vec3 aCo;\n" +"out vec3 aWorldCo;\n" +"\n" +"void main()\n" +"{\n" +" vec4 co_local = vec4( a_co, 1.0 );\n" +" vec3 co0 = uTransforms[ a_groups[0] ] * co_local;\n" +" vec3 co1 = uTransforms[ a_groups[1] ] * co_local;\n" +" vec3 co2 = uTransforms[ a_groups[2] ] * co_local;\n" +" vec3 n0 = mat3(uTransforms[ a_groups[0] ]) * a_norm;\n" +" vec3 n1 = mat3(uTransforms[ a_groups[1] ]) * a_norm;\n" +" vec3 n2 = mat3(uTransforms[ a_groups[2] ]) * a_norm;\n" +"\n" +" vec3 world_pos = co0*a_weights[0] + co1*a_weights[1] + co2*a_weights[2];\n" +" vec3 world_normal = n0*a_weights[0] + n1*a_weights[1] + n2*a_weights[2];\n" +"\n" +" gl_Position = uPv * vec4( world_pos, 1.0 );\n" +" aColour = a_colour;\n" +" aUv = a_uv;\n" +" aNorm = world_normal;\n" +" aCo = a_co;\n" +" aWorldCo = world_pos;\n" +"}\n" +""}, + .fs = +{ +.orig_file = "../../shaders/viewchar.fs", +.static_src = +"out vec4 FragColor;\n" +"\n" +"uniform sampler2D uTexMain;\n" +"\n" +"in vec4 aColour;\n" +"in vec2 aUv;\n" +"in vec3 aNorm;\n" +"in vec3 aCo;\n" +"in vec3 aWorldCo;\n" +"\n" +"#line 1 1 \n" +"layout (std140) uniform ub_world_lighting\n" +"{\n" +" vec4 g_light_colours[3];\n" +" vec4 g_light_directions[3];\n" +" vec4 g_ambient_colour;\n" +"\n" +" vec4 g_water_plane;\n" +" vec4 g_depth_bounds;\n" +" float g_water_fog;\n" +" int g_light_count;\n" +" int g_light_preview;\n" +"};\n" +"\n" +"uniform sampler2D g_world_depth;\n" +"\n" +"// Standard diffuse + spec models\n" +"// ==============================\n" +"\n" +"vec3 do_light_diffuse( vec3 vfrag, vec3 wnormal )\n" +"{\n" +" vec3 vtotal = g_ambient_colour.rgb;\n" +"\n" +" for( int i=0; irate; - frame = frame % anim->length; + float animtime = time*anim->rate; + + u32 frame = ((u32)animtime) % anim->length, + next = (frame+1) % anim->length; + + float t = vg_fractf( animtime ); + + mdl_keyframe *base = anim->anim_data + (skele->bone_count-1)*frame, + *nbase = anim->anim_data + (skele->bone_count-1)*next; - 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++ ) @@ -50,12 +68,20 @@ static void skeleton_apply_frame( m4x3f transform, v3f temp_delta; v3_sub( skele->bones[i].co, skele->bones[sb->parent].co, temp_delta ); - /* pose matrix */ - mdl_keyframe *kf = base+i-1; + mdl_keyframe *kf = base+i-1, + *nkf = nbase+i-1; + + v3f co; + v4f q; + v3f s; - q_m3x3( kf->q, posemtx ); - v3_copy( kf->co, posemtx[3] ); + v3_lerp( kf->co, nkf->co, t, co ); + q_nlerp( kf->q, nkf->q, t, q ); + v3_lerp( kf->s, nkf->s, t, s ); + + q_m3x3( q, posemtx ); + v3_copy( co, posemtx[3] ); v3_add( temp_delta, posemtx[3], posemtx[3] ); /* final matrix */ @@ -74,6 +100,119 @@ static void skeleton_apply_frame( m4x3f transform, } } +/* + * Get transformed position of bone + */ +static void skeleton_bone_posepos( struct skeleton *skele, u32 id, v3f co ) +{ + m4x3_mulv( skele->final_transforms[id], skele->bones[id].co, co ); +} + +/* + * creates the reference inverse matrix for an IK bone, as it has an initial + * intrisic rotation based on the direction that the IK is setup.. + */ +static void skeleton_inverse_for_ik( struct skeleton *skele, + v3f ivaxis, + u32 id, m4x3f inverse ) +{ + v3_copy( ivaxis, inverse[0] ); + v3_copy( skele->bones[id].end, inverse[1] ); + v3_normalize( inverse[1] ); + v3_cross( inverse[0], inverse[1], inverse[2] ); + v3_copy( skele->bones[id].co, inverse[3] ); + m4x3_invert_affine( inverse, inverse ); +} + +/* + * Apply all IK modifiers (2 bone ik reference from blender is supported) + */ +static void skeleton_apply_ik_pass( struct skeleton *skele ) +{ + for( int i=0; iik_count; i++ ) + { + struct skeleton_ik *ik = &skele->ik[i]; + + v3f v0, /* base -> target */ + v1, /* base -> pole */ + vaxis; + + v3f co_base, + co_target, + co_pole; + + skeleton_bone_posepos( skele, ik->lower, co_base ); + skeleton_bone_posepos( skele, ik->target, co_target ); + skeleton_bone_posepos( skele, ik->pole, co_pole ); + + v3_sub( co_target, co_base, v0 ); + v3_sub( co_pole, co_base, v1 ); + v3_cross( v0, v1, vaxis ); + v3_normalize( vaxis ); + v3_normalize( v0 ); + v3_cross( vaxis, v0, v1 ); + + /* localize problem into [x:v0,y:v1] 2d plane */ + v2f base = { v3_dot( v0, co_base ), v3_dot( v1, co_base ) }, + end = { v3_dot( v0, co_target ), v3_dot( v1, co_target ) }, + knee; + + /* Compute angles (basic trig)*/ + v2f delta; + v2_sub( end, base, delta ); + + float + l1 = v3_length( skele->bones[ik->lower].end ), + l2 = v3_length( skele->bones[ik->upper].end ), + d = vg_clampf( v2_length(delta), fabsf(l1 - l2), l1+l2-0.00001f ), + c = acosf( (l1*l1 + d*d - l2*l2) / (2.0f*l1*d) ), + rot = atan2f( delta[1], delta[0] ) + c - VG_PIf/2.0f; + + knee[0] = sinf(-rot) * l1; + knee[1] = cosf(-rot) * l1; + + m4x3_identity( skele->final_transforms[ik->lower] ); + m4x3_identity( skele->final_transforms[ik->upper] ); + + /* inverse matrix axis '(^axis,^bone,...)[base] */ + m4x3f inverse; + v3f iv0, iv1, ivaxis; + v3_sub( skele->bones[ik->target].co, skele->bones[ik->lower].co, iv0 ); + v3_sub( skele->bones[ik->pole].co, skele->bones[ik->lower].co, iv1 ); + v3_cross( iv0, iv1, ivaxis ); + v3_normalize( ivaxis ); + + skeleton_inverse_for_ik( skele, ivaxis, ik->lower, inverse ); + + /* create rotation matrix */ + v3f co_knee; + v3_muladds( co_base, v0, knee[0], co_knee ); + v3_muladds( co_knee, v1, knee[1], co_knee ); + vg_line( co_base, co_knee, 0xff00ff00 ); + + m4x3f transform; + v3_copy( vaxis, transform[0] ); + v3_muls( v0, knee[0], transform[1] ); + v3_muladds( transform[1], v1, knee[1], transform[1] ); + v3_normalize( transform[1] ); + v3_cross( transform[0], transform[1], transform[2] ); + v3_copy( co_base, transform[3] ); + + m4x3_mul( transform, inverse, skele->final_transforms[ik->lower] ); + + /* 'upper' or knee bone */ + skeleton_inverse_for_ik( skele, ivaxis, ik->upper, inverse ); + + v3_copy( vaxis, transform[0] ); + v3_sub( co_target, co_knee, transform[1] ); + v3_normalize( transform[1] ); + v3_cross( transform[0], transform[1], transform[2] ); + v3_copy( co_knee, transform[3] ); + + m4x3_mul( transform, inverse, skele->final_transforms[ik->upper] ); + } +} + static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele, const char *name ) { @@ -91,7 +230,7 @@ static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele, /* Setup an animated skeleton from model */ static int skeleton_setup( struct skeleton *skele, mdl_header *mdl ) { - u32 bone_count = 1, skeleton_root = 0; + u32 bone_count = 1, skeleton_root = 0, ik_count = 0; skele->bone_count = 0; skele->bones = NULL; skele->final_transforms = NULL; @@ -109,22 +248,64 @@ static int skeleton_setup( struct skeleton *skele, mdl_header *mdl ) if( skele->bone_count ) { vg_error( "Multiple skeletons in model file\n" ); - free( skele->bones ); - return 0; + goto error_dealloc; } skele->bone_count = inf->channels; + skele->ik_count = inf->ik_count; skele->bones = malloc(sizeof(struct skeleton_bone)*skele->bone_count); + skele->ik = malloc(sizeof(struct skeleton_ik)*skele->ik_count); skeleton_root = i; } else if( skele->bone_count ) { - if( pnode->classtype == k_classtype_bone ) + int is_ik = pnode->classtype == k_classtype_ik_bone, + is_bone = (pnode->classtype == k_classtype_bone) || is_ik; + + if( is_bone ) { - struct skeleton_bone *sb = &skele->bones[bone_count ++]; + if( bone_count == skele->bone_count ) + { + vg_error( "too many bones (%u/%u) @%s!\n", + bone_count, skele->bone_count, + mdl_pstr( mdl, pnode->pstr_name )); + + goto error_dealloc; + } + + 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; + + if( is_ik ) + { + struct classtype_ik_bone *ik_inf = mdl_get_entdata( mdl, pnode ); + sb->deform = ik_inf->deform; + sb->ik = 1; /* TODO: place into new IK array */ + skele->bones[ sb->parent ].ik = 1; + + if( ik_count == skele->ik_count ) + { + vg_error( "Too many ik bones, corrupt model file\n" ); + goto error_dealloc; + } + + struct skeleton_ik *ik = &skele->ik[ ik_count ++ ]; + ik->upper = bone_count; + ik->lower = sb->parent; + ik->target = ik_inf->target; + ik->pole = ik_inf->pole; + } + else + { + struct classtype_bone *bone_inf = mdl_get_entdata( mdl, pnode ); + sb->deform = bone_inf->deform; + sb->ik = 0; + } + + bone_count ++; } else { @@ -142,7 +323,13 @@ static int skeleton_setup( struct skeleton *skele, mdl_header *mdl ) if( bone_count != skele->bone_count ) { vg_error( "Loaded %u bones out of %u\n", bone_count, skele->bone_count ); - return 0; + goto error_dealloc; + } + + if( ik_count != skele->ik_count ) + { + vg_error( "Loaded %u ik bones out of %u\n", ik_count, skele->ik_count ); + goto error_dealloc; } /* fill in implicit root bone */ @@ -173,6 +360,11 @@ static int skeleton_setup( struct skeleton *skele, mdl_header *mdl ) vg_success( "Loaded skeleton with %u bones\n", skele->bone_count ); return 1; + +error_dealloc: + free( skele->bones ); + free( skele->ik ); + return 0; } static void skeleton_debug( struct skeleton *skele ) @@ -184,11 +376,24 @@ static void skeleton_debug( struct skeleton *skele ) v3f p0, p1; v3_copy( sb->co, p0 ); v3_add( p0, sb->end, p1 ); - vg_line( p0, p1, 0xffffffff ); + //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 ); + + if( sb->deform ) + { + if( sb->ik ) + { + vg_line( p0, p1, 0xff0000ff ); + } + else + { + vg_line( p0, p1, 0xffcccccc ); + } + } + else + vg_line( p0, p1, 0xff00ffff ); } }