+/*
+ * Copyright (C) Mount0 Software, Harry Godden - All Rights Reserved
+ */
+
#ifndef SKELETON_H
#define SKELETON_H
v3f co, end;
u32 parent;
- /* info, not real */
int deform, ik;
+ int defer;
mdl_keyframe kf;
+
+ u32 orig_node;
+
+ int collider;
+ boxf hitbox;
+
+ char name[16];
}
*bones;
- m4x3f *final_transforms;
+ m4x3f *final_mtx;
struct skeleton_ik
{
u32 lower, upper, target, pole;
+ m3x3f ia, ib;
}
*ik;
u32 bone_count,
ik_count,
+ collider_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. */
};
-static void skeleton_apply_frame( m4x3f transform,
- struct skeleton *skele,
- struct skeleton_anim *anim,
- float time )
+static u32 skeleton_bone_id( struct skeleton *skele, const char *name )
+{
+ for( u32 i=0; i<skele->bone_count; i++ )
+ {
+ if( !strcmp( skele->bones[i].name, name ))
+ return i;
+ }
+
+ return 0;
+}
+
+static void keyframe_copy_pose( mdl_keyframe *kfa, mdl_keyframe *kfb, int num )
+{
+ for( int i=0; i<num; i++ )
+ kfb[i] = kfa[i];
+}
+
+/*
+ * 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 )
+{
+ if( t <= 0.01f )
+ {
+ keyframe_copy_pose( kfa, kfd, count );
+ return;
+ }
+ else if( t >= 0.99f )
+ {
+ keyframe_copy_pose( kfb, kfd, count );
+ return;
+ }
+
+ for( int i=0; i<count; i++ )
+ {
+ v3_lerp( kfa[i].co, kfb[i].co, t, kfd[i].co );
+ q_nlerp( kfa[i].q, kfb[i].q, t, kfd[i].q );
+ v3_lerp( kfa[i].s, kfb[i].s, t, kfd[i].s );
+ }
+}
+
+static void skeleton_lerp_pose( struct skeleton *skele,
+ mdl_keyframe *kfa, mdl_keyframe *kfb, float t,
+ mdl_keyframe *kfd )
+{
+ keyframe_lerp_pose( kfa, kfb, t, kfd, skele->bone_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;
mdl_keyframe *base = anim->anim_data + (skele->bone_count-1)*frame,
*nbase = anim->anim_data + (skele->bone_count-1)*next;
- m4x3_copy( transform, skele->final_transforms[0] );
+ skeleton_lerp_pose( skele, base, nbase, t, output );
+}
+
+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;
+
+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; i<skele->bone_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;
v3_sub( skele->bones[i].co, skele->bones[sb->parent].co, temp_delta );
/* pose matrix */
- mdl_keyframe *kf = base+i-1,
- *nkf = nbase+i-1;
-
- v3f co;
- v4f q;
- v3f s;
-
- 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] );
+ 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] );
- }
-
- /* 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] );
+ m4x3_mul( skele->final_mtx[ sb->parent ], posemtx, skele->final_mtx[i] );
}
}
-/*
- * 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 )
+ 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] );
- v3_copy( skele->bones[id].co, inverse[3] );
- m4x3_invert_affine( inverse, inverse );
+ m3x3_transpose( inverse, inverse );
+}
+
+/*
+ * Creates inverse rotation matrices which the IK system uses.
+ */
+static void skeleton_create_inverses( struct skeleton *skele )
+{
+ /* IK: inverse 'plane-bone space' axis '(^axis,^bone,...)[base] */
+ for( int i=0; i<skele->ik_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 );
+ }
+}
+
+/*
+ * Apply a model matrix to all bones, should be done last
+ */
+static void skeleton_apply_transform( struct skeleton *skele, m4x3f transform )
+{
+ for( int i=0; i<skele->bone_count; i++ )
+ {
+ struct skeleton_bone *sb = &skele->bones[i];
+ m4x3_mul( transform, skele->final_mtx[i], skele->final_mtx[i] );
+ }
+}
+
+/*
+ * Apply an inverse matrix to all bones which maps vertices from bind space into
+ * bone relative positions
+ */
+static void skeleton_apply_inverses( struct skeleton *skele )
+{
+ for( int i=0; i<skele->bone_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] );
+ }
}
/*
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_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 );
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 );
+ m4x3_identity( skele->final_mtx[ik->lower] );
+ m4x3_identity( skele->final_mtx[ik->upper] );
/* create rotation matrix */
v3f co_knee;
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 );
+ 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] );
- m4x3_mul( transform, inverse, skele->final_transforms[ik->upper] );
+ 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
+ */
+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
+ */
static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele,
const char *name )
{
/* 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;
+ 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;
skele->bone_count = inf->channels;
skele->ik_count = inf->ik_count;
+ skele->collider_count = inf->collider_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;
+ int is_bone = pnode->classtype == k_classtype_bone;
if( is_bone )
{
}
struct skeleton_bone *sb = &skele->bones[bone_count];
+ struct classtype_bone *bone_inf = mdl_get_entdata( mdl, pnode );
+ int is_ik = bone_inf->ik_target;
v3_copy( pnode->co, sb->co );
v3_copy( pnode->s, sb->end );
sb->parent = pnode->parent-skeleton_root;
+ strncpy( sb->name, mdl_pstr(mdl,pnode->pstr_name), 15 );
+ sb->deform = bone_inf->deform;
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;
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;
+ ik->target = bone_inf->ik_target;
+ ik->pole = bone_inf->ik_pole;
}
else
{
- struct classtype_bone *bone_inf = mdl_get_entdata( mdl, pnode );
- sb->deform = bone_inf->deform;
sb->ik = 0;
}
+ sb->collider = bone_inf->collider;
+ sb->orig_node = i;
+ box_copy( bone_inf->hitbox, sb->hitbox );
+
+ if( bone_inf->collider )
+ {
+ if( collider_count == skele->collider_count )
+ {
+ vg_error( "Too many collider bones\n" );
+ goto error_dealloc;
+ }
+
+ collider_count ++;
+ }
+
bone_count ++;
}
else
return 0;
}
+ if( collider_count != skele->collider_count )
+ {
+ vg_error( "Loaded %u colliders out of %u\n", collider_count,
+ skele->collider_count );
+ goto error_dealloc;
+ }
+
if( bone_count != skele->bone_count )
{
vg_error( "Loaded %u bones out of %u\n", bone_count, skele->bone_count );
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].collider = 0;
- skele->final_transforms = malloc( sizeof(m4x3f) * skele->bone_count );
+ 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);
memcpy( dst, mdl_get_animdata( mdl, anim ), block_size );
}
+ skeleton_create_inverses( skele );
vg_success( "Loaded skeleton with %u bones\n", skele->bone_count );
+ vg_success( " %u colliders\n", skele->collider_count );
return 1;
error_dealloc:
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 );
+ m4x3_mulv( skele->final_mtx[i], p0, p0 );
+ m4x3_mulv( skele->final_mtx[i], p1, p1 );
if( sb->deform )
{