+ vg_error( "skeleton_bone_id( *, \"%s\" );\n", name );
+ vg_fatal_error( "Bone does not exist\n" );
+
+ return 0;
+}
+
+VG_STATIC void keyframe_copy_pose( mdl_keyframe *kfa, mdl_keyframe *kfb,
+ int num )
+{
+ for( int i=0; i<num; i++ )
+ kfb[i] = kfa[i];
+}
+
+
+/* apply a rotation from the perspective of root */
+VG_STATIC void keyframe_rotate_around( mdl_keyframe *kf,
+ v3f origin, v3f offset, v4f q )
+{
+ v3f v0, co;
+ v3_add( kf->co, offset, co );
+ v3_sub( co, origin, v0 );
+ q_mulv( q, v0, v0 );
+ v3_add( v0, origin, co );
+ v3_sub( co, offset, kf->co );
+
+ q_mul( q, kf->q, kf->q );
+ q_normalize( kf->q );
+}
+
+/*
+ * Lerp between two sets of keyframes and store in dest. Rotations use Nlerp.
+ */
+VG_STATIC void keyframe_lerp_pose( mdl_keyframe *kfa, mdl_keyframe *kfb,
+ float t, mdl_keyframe *kfd, int count )
+{
+ if( t <= 0.0001f ){
+ keyframe_copy_pose( kfa, kfd, count );
+ return;
+ }
+ else if( t >= 0.9999f ){
+ 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 );
+ }
+}
+
+VG_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 );
+}
+
+VG_STATIC void skeleton_copy_pose( struct skeleton *skele,
+ mdl_keyframe *kfa, mdl_keyframe *kfd )
+{
+ keyframe_copy_pose( kfa, kfd, skele->bone_count-1 );
+}