X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=skeleton.h;h=4f35d58e25d50681b462cfac7596f3a8b02991da;hb=2a238d32da833812e837cf38e16a7685c98db5c3;hp=4fb30988f6e2e3d3ef2b9bebd5b314f92f1e629f;hpb=1361a6d6ffda17feca6395beccf269763d3a76fa;p=carveJwlIkooP6JGAAIwe30JlM.git diff --git a/skeleton.h b/skeleton.h index 4fb3098..4f35d58 100644 --- a/skeleton.h +++ b/skeleton.h @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ + #ifndef SKELETON_H #define SKELETON_H @@ -8,41 +12,189 @@ struct skeleton struct skeleton_bone { v3f co, end; - u32 children; /* maybe remove */ u32 parent; - + + u32 flags; + int defer; + mdl_keyframe kf; + + u32 orig_node; + + boxf hitbox; + const char *name; } *bones; - m4x3f *final_transforms; + u32 bone_count; struct skeleton_anim { - float rate; + const char *name; u32 length; - struct mdl_keyframe *anim_data; - char name[32]; + + float rate; + mdl_keyframe *anim_data; } *anims; + u32 anim_count; - u32 bone_count, - anim_count; + m4x3f *final_mtx; + + struct skeleton_ik + { + u32 lower, upper, target, pole; + m3x3f ia, ib; + } + *ik; + u32 ik_count; + + u32 + collider_count, + bindable_count; }; -static void skeleton_apply_frame( m4x3f transform, - struct skeleton *skele, - struct skeleton_anim *anim, - float time ) +VG_STATIC u32 skeleton_bone_id( struct skeleton *skele, const char *name ) { - u32 frame = time*anim->rate; - frame = frame % anim->length; + for( u32 i=1; ibone_count; i++ ) + { + if( !strcmp( skele->bones[i].name, name )) + return i; + } - mdl_keyframe *base = anim->anim_data + (skele->bone_count-1)*frame; - m4x3_copy( transform, skele->final_transforms[0] ); + return 0; +} + +VG_STATIC void keyframe_copy_pose( mdl_keyframe *kfa, mdl_keyframe *kfb, int num ) +{ + for( int i=0; i= 0.99f ) + { + keyframe_copy_pose( kfb, kfd, count ); + return; + } + + 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 + */ +VG_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 ); +} + +VG_STATIC int skeleton_sample_anim_clamped( struct skeleton *skele, + struct skeleton_anim *anim, + float time, + mdl_keyframe *output ) +{ + float end = (float)(anim->length-1) / anim->rate; + skeleton_sample_anim( skele, anim, vg_minf( end, time ), output ); + + if( time > end ) + return 0; + else + return 1; +} + +typedef enum anim_apply +{ + k_anim_apply_always, + k_anim_apply_defer_ik, + k_anim_apply_deffered_only +} +anim_apply; + +VG_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->flags & k_bone_flag_ik) && !(sb->flags & k_bone_flag_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 + */ +VG_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].flags &= ~k_bone_flag_ik; for( int i=1; ibone_count; i++ ) { - struct skeleton_bone *sb = &skele->bones[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; @@ -50,31 +202,180 @@ 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 = &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] ); + m4x3_mul( skele->final_mtx[ sb->parent ], posemtx, skele->final_mtx[i] ); } +} - /* armature space -> bone space matrix ( for verts ) */ - for( int i=1; ibone_count; 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.. + */ +VG_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 ); +} + +/* + * Creates inverse rotation matrices which the IK system uses. + */ +VG_STATIC void skeleton_create_inverses( struct skeleton *skele ) +{ + /* IK: inverse 'plane-bone space' axis '(^axis,^bone,...)[base] */ + for( int i=0; iik_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] ); + 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 ); + } +} + +/* + * Apply a model matrix to all bones, should be done last + */ +VG_STATIC void skeleton_apply_transform( struct skeleton *skele, m4x3f transform ) +{ + 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 struct skeleton_anim *skeleton_get_anim( struct skeleton *skele, +/* + * Apply an inverse matrix to all bones which maps vertices from bind space into + * bone relative positions + */ +VG_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) + */ +VG_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] ); + } +} + +/* + * Applies the typical operations that you want for an IK rig: + * Pose, IK, Pose(deferred), Inverses, Transform + */ +VG_STATIC void skeleton_apply_standard( struct skeleton *skele, mdl_keyframe *pose, + m4x3f transform ) +{ + skeleton_apply_pose( skele, pose, k_anim_apply_defer_ik ); + skeleton_apply_ik_pass( skele ); + skeleton_apply_pose( skele, pose, k_anim_apply_deffered_only ); + skeleton_apply_inverses( skele ); + skeleton_apply_transform( skele, transform ); +} + +/* + * Get an animation by name + */ +VG_STATIC struct skeleton_anim *skeleton_get_anim( struct skeleton *skele, const char *name ) { for( int i=0; ianim_count; i++ ) @@ -88,43 +389,109 @@ static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele, return NULL; } -/* Setup an animated skeleton from model */ -static int skeleton_setup( struct skeleton *skele, mdl_header *mdl ) +VG_STATIC void skeleton_alloc_from( struct skeleton *skele, + void *lin_alloc, + struct classtype_skeleton *inf ) { - u32 bone_count = 1, skeleton_root = 0; + skele->bone_count = inf->channels; + skele->ik_count = inf->ik_count; + skele->collider_count = inf->collider_count; + skele->anim_count = inf->anim_count; + + u32 bone_size = sizeof(struct skeleton_bone) * skele->bone_count, + ik_size = sizeof(struct skeleton_ik) * skele->ik_count, + mtx_size = sizeof(m4x3f) * skele->bone_count, + anim_size = sizeof(struct skeleton_anim) * skele->anim_count; + + skele->bones = vg_linear_alloc( lin_alloc, bone_size ); + skele->ik = vg_linear_alloc( lin_alloc, ik_size ); + skele->final_mtx = vg_linear_alloc( lin_alloc, mtx_size ); + skele->anims = vg_linear_alloc( lin_alloc, anim_size ); +} + +VG_STATIC void skeleton_fatal_err(void) +{ + vg_fatal_exit_loop( "Skeleton setup failed" ); +} + +/* Setup an animated skeleton from model. mdl's metadata should stick around */ +VG_STATIC void skeleton_setup( struct skeleton *skele, + void *lin_alloc, mdl_context *mdl ) +{ + u32 bone_count = 1, skeleton_root = 0, ik_count = 0, collider_count = 0; skele->bone_count = 0; skele->bones = NULL; - skele->final_transforms = NULL; + skele->final_mtx = NULL; skele->anims = NULL; struct classtype_skeleton *inf = NULL; - for( u32 i=0; inode_count; i++ ) + for( u32 i=0; iinfo.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_alloc_from( skele, lin_alloc, inf ); skeleton_root = i; } else if( skele->bone_count ) { - if( pnode->classtype == k_classtype_bone ) + int is_bone = pnode->classtype == k_classtype_bone; + + 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 )); + + skeleton_fatal_err(); + } + + struct skeleton_bone *sb = &skele->bones[bone_count]; + struct classtype_bone *bone_inf = mdl_get_entdata( mdl, pnode ); + v3_copy( pnode->co, sb->co ); v3_copy( pnode->s, sb->end ); sb->parent = pnode->parent-skeleton_root; + sb->name = mdl_pstr( mdl, pnode->pstr_name ); + sb->flags = bone_inf->flags; + + if( sb->flags & k_bone_flag_ik ) + { + skele->bones[ sb->parent ].flags |= k_bone_flag_ik; + + if( ik_count == skele->ik_count ) + { + vg_error( "Too many ik bones, corrupt model file\n" ); + skeleton_fatal_err(); + } + + struct skeleton_ik *ik = &skele->ik[ ik_count ++ ]; + ik->upper = bone_count; + ik->lower = sb->parent; + ik->target = bone_inf->ik_target; + ik->pole = bone_inf->ik_pole; + } + + sb->orig_node = i; + box_copy( bone_inf->hitbox, sb->hitbox ); + + if( bone_inf->flags & k_bone_flag_collider_any ) + { + if( collider_count == skele->collider_count ) + { + vg_error( "Too many collider bones\n" ); + skeleton_fatal_err(); + } + + collider_count ++; + } + + bone_count ++; } else { @@ -136,46 +503,57 @@ static int skeleton_setup( struct skeleton *skele, mdl_header *mdl ) if( !inf ) { vg_error( "No skeleton in model\n" ); - return 0; + skeleton_fatal_err(); + } + + if( collider_count != skele->collider_count ) + { + vg_error( "Loaded %u colliders out of %u\n", collider_count, + skele->collider_count ); + skeleton_fatal_err(); } if( bone_count != skele->bone_count ) { vg_error( "Loaded %u bones out of %u\n", bone_count, skele->bone_count ); - return 0; + vg_fatal_exit_loop( "Skeleton setup failed" ); + skeleton_fatal_err(); + } + + if( ik_count != skele->ik_count ) + { + vg_error( "Loaded %u ik bones out of %u\n", ik_count, skele->ik_count ); + skeleton_fatal_err(); } /* 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->bones[0].flags = 0; + skele->bones[0].name = "[root]"; - 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++ ) + /* process animation quick refs */ + for( int i=0; ianim_count; i++ ) { - mdl_animation *anim = - mdl_animation_from_id( mdl, inf->anim_start+i ); + mdl_animation *anim = &mdl->anim_buffer[ 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 ); + skele->anims[i].rate = anim->rate; + skele->anims[i].length = anim->length; + skele->anims[i].name = mdl_pstr(mdl, anim->pstr_name); + skele->anims[i].anim_data = &mdl->keyframe_buffer[ anim->offset ]; - 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_info( "animation[ %f, %u ] '%s'\n", anim->rate, + anim->length, + skele->anims[i].name ); } + skeleton_create_inverses( skele ); vg_success( "Loaded skeleton with %u bones\n", skele->bone_count ); - return 1; + vg_success( " %u colliders\n", skele->collider_count ); } -static void skeleton_debug( struct skeleton *skele ) +VG_STATIC void skeleton_debug( struct skeleton *skele ) { for( int i=0; ibone_count; i ++ ) { @@ -184,11 +562,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 ); + m4x3_mulv( skele->final_mtx[i], p0, p0 ); + m4x3_mulv( skele->final_mtx[i], p1, p1 ); + + if( sb->flags & k_bone_flag_deform ) + { + if( sb->flags & k_bone_flag_ik ) + { + vg_line( p0, p1, 0xff0000ff ); + } + else + { + vg_line( p0, p1, 0xffcccccc ); + } + } + else + vg_line( p0, p1, 0xff00ffff ); } }