From 4b222b13c504e5c21a743b7aeb02a692ce16da01 Mon Sep 17 00:00:00 2001 From: hgn Date: Fri, 9 Sep 2022 01:04:37 +0100 Subject: [PATCH] Ik revision --- anim_test.h | 22 +- models_src/ch_new.mdl | Bin 269193 -> 270380 bytes skeleton.h | 663 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 681 insertions(+), 4 deletions(-) diff --git a/anim_test.h b/anim_test.h index c2f3eae..86c6853 100644 --- a/anim_test.h +++ b/anim_test.h @@ -8,7 +8,7 @@ static struct { struct skeleton skele; - struct skeleton_anim *yay; + struct skeleton_anim *anim_stand, *anim_highg; glmesh mesh; } @@ -18,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, "pose_stand" ); + animtest.anim_stand = skeleton_get_anim( &animtest.skele, "pose_stand" ); + animtest.anim_highg = skeleton_get_anim( &animtest.skele, "pose_highg" ); mdl_unpack_glmesh( johannes, &animtest.mesh ); free( johannes ); @@ -36,8 +37,21 @@ static void anim_test_update(void) 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 ); + mdl_keyframe apose[32], + bpose[32]; + + float a = sinf(vg_time)*0.5f+0.5f, + b = (sinf(vg_time*2.0f)*0.5f+0.5f)*(15.0f/30.0f); + + skeleton_sample_anim( &animtest.skele, animtest.anim_stand, b, apose ); + skeleton_sample_anim( &animtest.skele, animtest.anim_highg, b, bpose ); + + skeleton_lerp_pose( &animtest.skele, apose, bpose, a, apose ); + skeleton_apply_pose( &animtest.skele, apose, k_anim_apply_defer_ik ); skeleton_apply_ik_pass( &animtest.skele ); + skeleton_apply_pose( &animtest.skele, apose, k_anim_apply_deffered_only ); + skeleton_apply_inverses( &animtest.skele ); + skeleton_apply_transform( &animtest.skele, transform ); skeleton_debug( &animtest.skele ); } @@ -62,7 +76,7 @@ static void anim_test_render( vg_tex2d *tex ) glUniformMatrix4x3fv( _uniform_viewchar_uTransforms, animtest.skele.bone_count, 0, - (float *)animtest.skele.final_transforms ); + (float *)animtest.skele.final_mtx ); mesh_bind( &animtest.mesh ); mesh_draw( &animtest.mesh ); diff --git a/models_src/ch_new.mdl b/models_src/ch_new.mdl index d4cf5c7ba9b373df9c6130f99aa2b3774e4be1ff..234f50c8ef89be9a0df7505a02b1943708060868 100644 GIT binary patch delta 10570 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 delta 2454 zcmYk83se(V9>yn30vH~d)6zwfgXkJS1rcZKa;9ZSTScjC*D4D2iTD63DB@$OkN_)9 z3A&X7vpT+z1r@3&#z74iOaf>Gr685A^6a95T^~?<@p21$bD0t5ocX@FlXJe`{Qr0E z+?=l@G7W?qhSe8i@W@n%v3<@MCU(HE2yYDAlOJKh|i~BMk+7mD-*j^M6Aqf~Z1zw5K7{hFszf0dwS4%V0QV~3D z3~DL9Jlp`EqDxE!hPfL$KFJJZHN#Y0f2xXAZ@?$Zrr(mzah1xApRRqTKmAiGTak#% zoz8h1BKvXz^~u9v*Bis)20R5uqj8;HDxsz;a|&)8WUo7#FEh3D9^lSh-_94+f2O~zMrjx1()1FTsx^F3J8vFE`Cc^@*U zQQpLbkzer~B^?fIUEd2jlJpRqj1o8j;uR==Ubs8*5B^h%I6sucPvEekZ-D=hFTL$@ z3#GFN9KS_p$dPDA^J(%vH=hXSIi$mSp#9P@NISU`G#wH+P6Kf$Kf`to^4HZhh$Fum zJI*n*->}Y6d3q*TyTDKp9sd(GI3m!9qZi4|_hsg`Qym<8BaEkgcWyiA_8g>Z7qbId9`g&=Rwv}Q#wCJY=Q}jp(E|MSLVldK0OjVn>_EPbv^85-&tuW5 z33ceCM*!_zX#7oCLk)Nfb(nk4lS56;=Ii1P?(weWe7_=XlR&=$&;=eX!2C+!Y_2Ot z`RO$pY9{I|JaOVo&QO=2~r77O2mt6c@8vdh7o$ll{RHuLX- zv-yG=oT9Vng{zxES|q>uteIVbAR9l~k9^NMR|E^+{a4g9V(-g5fFCAIw_7WzxOff! zwokz<=L32E5~_^Y_{id z@|3H+xqe3@H~;6a*w`(wqGcKo;`0TamI&8gq`Oh9{4}^GO)e? diff --git a/skeleton.h b/skeleton.h index ace80e6..ac368cf 100644 --- a/skeleton.h +++ b/skeleton.h @@ -3,6 +3,543 @@ #include "model.h" +struct skeleton +{ + struct skeleton_bone + { + v3f co, end; + u32 parent; + + int deform, ik; + int defer; + + mdl_keyframe kf; + } + *bones; + m4x3f *final_mtx; + + struct skeleton_ik + { + u32 lower, upper, target, pole; + m3x3f ia, ib; + } + *ik; + + struct skeleton_anim + { + float rate; + u32 length; + struct mdl_keyframe *anim_data; + char name[32]; + } + *anims; + + u32 bone_count, + ik_count, + anim_count, + bindable_count; /* TODO: try to place IK last in the rig from export + so that we dont always upload transforms for + useless cpu IK bones. */ +}; + +/* + * Lerp between two sets of keyframes and store in dest. Rotations use Nlerp. + */ +static void keyframe_lerp_pose( mdl_keyframe *kfa, mdl_keyframe *kfb, float t, + mdl_keyframe *kfd, int count ) +{ + for( int i=0; ibone_count-1 ); +} + +/* + * Sample animation between 2 closest frames using time value. Output is a + * keyframe buffer that is allocated with an appropriate size + */ +static void skeleton_sample_anim( struct skeleton *skele, + struct skeleton_anim *anim, + float time, + mdl_keyframe *output ) +{ + 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; + + skeleton_lerp_pose( skele, base, nbase, t, output ); +} + +typedef enum anim_apply +{ + k_anim_apply_always, + k_anim_apply_defer_ik, + k_anim_apply_deffered_only +} +anim_apply; + +static int should_apply_bone( struct skeleton *skele, u32 id, anim_apply type ) +{ + struct skeleton_bone *sb = &skele->bones[ id ], + *sp = &skele->bones[ sb->parent ]; + + if( type == k_anim_apply_defer_ik ) + { + if( (sp->ik && !sb->ik) || sp->defer ) + { + sb->defer = 1; + return 0; + } + else + { + sb->defer = 0; + return 1; + } + } + else if( type == k_anim_apply_deffered_only ) + { + if( sb->defer ) + return 1; + else + return 0; + } + + return 1; +} + +/* + * Apply block of keyframes to skeletons final pose + */ +static void skeleton_apply_pose( struct skeleton *skele, mdl_keyframe *pose, + anim_apply passtype ) +{ + m4x3_identity( skele->final_mtx[0] ); + skele->bones[0].defer = 0; + skele->bones[0].ik = 0; + + for( int i=1; ibone_count; i++ ) + { + struct skeleton_bone *sb = &skele->bones[i], + *sp = &skele->bones[ sb->parent ]; + + if( !should_apply_bone( skele, i, passtype ) ) + continue; + + sb->defer = 0; + + /* 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 = &pose[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_mtx[ sb->parent ], posemtx, skele->final_mtx[i] ); + } +} + +/* + * 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, m3x3f 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] ); + m3x3_transpose( inverse, inverse ); +} + +static void skeleton_create_inverses( struct skeleton *skele ) +{ + /* IK: inverse 'plane-bone space' axis '(^axis,^bone,...)[base] */ + for( int i=0; iik_count; i++ ) + { + struct skeleton_ik *ik = &skele->ik[i]; + + 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, ik->ia ); + skeleton_inverse_for_ik( skele, ivaxis, ik->upper, ik->ib ); + } +} + +static void skeleton_apply_transform( struct skeleton *skele, m4x3f transform ) +{ + /* bone space inverse matrix */ + for( int i=0; ibone_count; i++ ) + { + struct skeleton_bone *sb = &skele->bones[i]; + m4x3_mul( transform, skele->final_mtx[i], skele->final_mtx[i] ); + } +} + +static void skeleton_apply_inverses( struct skeleton *skele ) +{ + for( int i=0; ibone_count; i++ ) + { + struct skeleton_bone *sb = &skele->bones[i]; + m4x3f inverse; + m3x3_identity( inverse ); + v3_negate( sb->co, inverse[3] ); + + m4x3_mul( skele->final_mtx[i], inverse, skele->final_mtx[i] ); + } +} + +/* + * 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; + + v3_copy( skele->final_mtx[ik->lower][3], co_base ); + v3_copy( skele->final_mtx[ik->target][3], co_target ); + v3_copy( skele->final_mtx[ik->pole][3], 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_mtx[ik->lower] ); + m4x3_identity( skele->final_mtx[ik->upper] ); + + /* 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] ); + + m3x3_mul( transform, ik->ia, transform ); + m4x3_copy( transform, skele->final_mtx[ik->lower] ); + + /* upper/knee bone */ + 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] ); + + m3x3_mul( transform, ik->ib, transform ); + m4x3_copy( transform, skele->final_mtx[ik->upper] ); + } +} + +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, ik_count = 0; + skele->bone_count = 0; + skele->bones = NULL; + skele->final_mtx = 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" ); + 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 ) + { + int is_ik = pnode->classtype == k_classtype_ik_bone, + is_bone = (pnode->classtype == k_classtype_bone) || is_ik; + + if( is_bone ) + { + 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 + { + 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 ); + 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 */ + 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_mtx = 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 ); + } + + skeleton_create_inverses( skele ); + 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 ) +{ + 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_mtx[i], p0, p0 ); + m4x3_mulv( skele->final_mtx[i], p1, p1 ); + + if( sb->deform ) + { + if( sb->ik ) + { + vg_line( p0, p1, 0xff0000ff ); + } + else + { + vg_line( p0, p1, 0xffcccccc ); + } + } + else + vg_line( p0, p1, 0xff00ffff ); + } +} + +#endif /* SKELETON_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#if 0 +#ifndef SKELETON_H +#define SKELETON_H + +#include "model.h" + struct skeleton { struct skeleton_bone @@ -12,6 +549,7 @@ struct skeleton /* info, not real */ int deform, ik; + int defer; mdl_keyframe kf; } @@ -41,6 +579,130 @@ struct skeleton useless cpu IK bones. */ }; +/* + * Lerp between two sets of keyframes and store in dest. Rotations use Nlerp. + */ +static void keyframe_lerp_pose( mdl_keyframe *kfa, mdl_keyframe *kfb, float t, + mdl_keyframe *kfd, int count ) +{ + for( int i=0; ibone_count-1 ); +} + +/* + * Sample animation between 2 closest frames using time value. Output is a + * keyframe buffer that is allocated with an appropriate size + */ +static void skeleton_sample_anim( struct skeleton *skele, + struct skeleton_anim *anim, + float time, + mdl_keyframe *output ) +{ + 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; + + skeleton_lerp_pose( skele, base, nbase, t, output ); +} + +typedef enum anim_apply +{ + k_anim_apply_always, + k_anim_apply_defer_ik, + k_anim_apply_deffered_only +} +anim_apply; + +static int should_apply_bone( struct skeleton *skele, u32 id, anim_apply type ) +{ + struct skeleton_bone *sb = &skele->bones[ id ], + *sp = &skele->bones[ sb->parent ]; + + if( type == k_anim_apply_defer_ik ) + { + if( sp->ik || sp->defer ) + { + sb->defer = 1; + return 0; + } + } + else if( type == k_anim_apply_deffered_only ) + { + if( !sp->defer ) + return 0; + } + + return 1; +} + +/* + * Apply block of keyframes to skeletons final pose + */ +static void skeleton_apply_pose( m4x3f transform, + struct skeleton *skele, mdl_keyframe *pose, + anim_apply passtype ) +{ + m4x3_copy( transform, skele->final_transforms[0] ); + skele->bones[0].defer = 0; + skele->bones[0].ik = 0; + + for( int i=1; ibone_count; i++ ) + { + struct skeleton_bone *sb = &skele->bones[i], + *sp = &skele->bones[ sb->parent ]; + + if( !should_apply_bone( skele, i, passtype ) ) + continue; + + sb->defer = 0; + + /* 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 = &pose[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] ); + } + + /* bone space inverse matrix ( for verts ) TODO: move to seperate pass */ + for( int i=1; ibone_count; i++ ) + { + if( !should_apply_bone( skele, i, passtype ) ) + continue; + + 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 void skeleton_apply_frame( m4x3f transform, struct skeleton *skele, struct skeleton_anim *anim, @@ -398,3 +1060,4 @@ static void skeleton_debug( struct skeleton *skele ) } #endif /* SKELETON_H */ +#endif -- 2.25.1