+ pose->type = k_player_pose_type_ik;
+ v3_copy( animator->root_co, pose->root_co );
+ v4_copy( animator->root_q, pose->root_q );
+
+ /* transform */
+ v3f ext_up,ext_co;
+ q_mulv( pose->root_q, (v3f){0.0f,1.0f,0.0f}, ext_up );
+ v3_copy( pose->root_co, ext_co );
+ v3_muladds( pose->root_co, ext_up, -0.1f, pose->root_co );
+
+ /* apply flip rotation at midpoint */
+ q_mul( animator->qflip, pose->root_q, pose->root_q );
+ q_normalize( pose->root_q );
+
+ v3f rotation_point, rco;
+ v3_muladds( ext_co, ext_up, 0.5f, rotation_point );
+ v3_sub( pose->root_co, rotation_point, rco );
+
+ q_mulv( animator->qflip, rco, rco );
+ v3_add( rco, rotation_point, pose->root_co );
+
+ /* ANIMATIONS
+ * ---------------------------------------------------------------------- */
+
+ mdl_keyframe apose[32], bpose[32];
+ mdl_keyframe ground_pose[32];
+ {
+ /* stand/crouch */
+ f32 dir_frame = animator->z * (15.0f/30.0f),
+ stand_blend = animator->offset[1]*-2.0f;
+
+ pose->board.lean = animator->board_lean;
+
+ stand_blend = vg_clampf( 1.0f-animator->local_cog[1], 0, 1 );
+
+ skeleton_sample_anim( sk, player_skate.anim_stand, dir_frame, apose );
+ skeleton_sample_anim( sk, player_skate.anim_highg, dir_frame, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
+
+ /* sliding */
+ f32 slide_frame = animator->x * 0.25f + 0.25f;
+ skeleton_sample_anim( sk, player_skate.anim_slide, slide_frame, bpose );
+
+ mdl_keyframe mirrored[32];
+ player_mirror_pose( bpose, mirrored );
+ skeleton_lerp_pose( sk, bpose, mirrored, animator->z, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->slide, apose );
+
+ if( animator->reverse > 0.0f ){
+ skeleton_sample_anim( sk, player_skate.anim_push, animator->push_time,
+ bpose );
+ }
+ else{
+ skeleton_sample_anim( sk, player_skate.anim_push_reverse,
+ animator->push_time, bpose );
+ }
+ skeleton_lerp_pose( sk, apose, bpose, animator->push, apose );
+
+ struct skeleton_anim *jump_anim = animator->jump_dir?
+ player_skate.anim_ollie:
+ player_skate.anim_ollie_reverse;
+
+ f32 setup_blend = vg_minf( animator->jump, 1.0f );
+ skeleton_sample_anim_clamped( sk, jump_anim, animator->jump_time, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
+ }
+
+ mdl_keyframe air_pose[32];
+ {
+ float air_frame = (animator->airdir*0.5f+0.5f) * (15.0f/30.0f);
+ skeleton_sample_anim( sk, player_skate.anim_air, air_frame, apose );
+
+ float ang = atan2f( animator->grab[0], animator->grab[1] ),
+ ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
+ grab_frame = ang_unit * (15.0f/30.0f);
+
+ skeleton_sample_anim( sk, player_skate.anim_grabs, grab_frame, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->grabbing, air_pose );
+ }
+
+ skeleton_lerp_pose( sk, ground_pose, air_pose, animator->fly,
+ pose->keyframes );
+
+ mdl_keyframe *kf_board = &pose->keyframes[localplayer.id_board-1],
+ *kf_foot_l = &pose->keyframes[localplayer.id_ik_foot_l-1],
+ *kf_foot_r = &pose->keyframes[localplayer.id_ik_foot_r-1],
+ *kf_knee_l = &pose->keyframes[localplayer.id_ik_knee_l-1],
+ *kf_knee_r = &pose->keyframes[localplayer.id_ik_knee_r-1],
+ *kf_hip = &pose->keyframes[localplayer.id_hip-1],
+ *kf_wheels[] = { &pose->keyframes[localplayer.id_wheel_r-1],
+ &pose->keyframes[localplayer.id_wheel_l-1] };
+
+
+ mdl_keyframe grind_pose[32];
+ {
+ f32 frame = animator->grind_balance * 0.5f;
+
+ skeleton_sample_anim( sk, player_skate.anim_grind, frame, apose );
+ skeleton_sample_anim( sk, player_skate.anim_grind_jump, frame, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->jump, grind_pose );
+ }
+ skeleton_lerp_pose( sk, pose->keyframes, grind_pose,
+ animator->grind, pose->keyframes );
+ float add_grab_mod = 1.0f - animator->fly;
+
+ /* additive effects */
+ u32 apply_to[] = { localplayer.id_hip,
+ localplayer.id_ik_hand_l,
+ localplayer.id_ik_hand_r,
+ localplayer.id_ik_elbow_l,
+ localplayer.id_ik_elbow_r };
+
+ float apply_rates[] = { 1.0f,
+ 0.75f,
+ 0.75f,
+ 0.75f,
+ 0.75f };
+
+ for( int i=0; i<vg_list_size(apply_to); i ++ ){
+ pose->keyframes[apply_to[i]-1].co[0] += animator->offset[0]*add_grab_mod;
+ pose->keyframes[apply_to[i]-1].co[2] += animator->offset[2]*add_grab_mod;
+ }
+
+#if 1
+ /* angle 'correction' */
+ v3f origin;
+ v3_add( sk->bones[localplayer.id_hip].co, kf_hip->co, origin );
+
+ for( int i=0; i<vg_list_size(apply_to); i ++ ){
+ mdl_keyframe *kf = &pose->keyframes[apply_to[i]-1];
+ keyframe_rotate_around( kf, origin, sk->bones[apply_to[i]].co,
+ animator->qfixuptotal );
+ }
+#endif
+
+
+ if( animator->activity == k_skate_activity_handplant ){
+ struct skeleton_anim *anim = player_skate.anim_handplant;
+
+ mdl_keyframe hpose[32];
+ skeleton_sample_anim_clamped( sk, anim, animator->handplant_t, hpose );
+ if( animator->reverse < 0.0f )
+ player_mirror_pose( hpose, hpose );
+
+ mdl_keyframe *kf_world = &hpose[ localplayer.id_world -1 ];
+ m4x3f world, mmdl, world_view;
+ q_m3x3( kf_world->q, world );
+ v3_copy( kf_world->co, world[3] );
+
+ q_m3x3( pose->root_q, mmdl );
+ v3_copy( pose->root_co, mmdl[3] );
+
+ m4x3_mul( mmdl, world, world_view );
+
+ vg_line_arrow( world_view[3], world_view[0], 1.0f, 0xff0000ff );
+ vg_line_arrow( world_view[3], world_view[1], 1.0f, 0xff00ff00 );
+ vg_line_arrow( world_view[3], world_view[2], 1.0f, 0xffff0000 );
+
+ m4x3f invworld;
+ m4x3_invert_affine( world, invworld );
+ m4x3_mul( mmdl, invworld, world_view );
+
+ m3x3_q( world_view, pose->root_q );
+ v3_copy( world_view[3], pose->root_co );
+
+ f32 t = animator->handplant_t,
+ frames = anim->length-1,
+ length = animator->activity == k_skate_activity_handplant?
+ frames / anim->rate:
+ 999999,
+ end_dist = vg_minf( t, length - t )/k_anim_transition,
+ blend = vg_smoothstepf( vg_minf(1,end_dist) );
+
+ skeleton_lerp_pose( sk, pose->keyframes, hpose, blend, pose->keyframes );
+ }
+
+
+ /* trick rotation */
+ v4f qtrick, qyaw, qpitch, qroll;
+ q_axis_angle( qyaw, (v3f){0.0f,1.0f,0.0f}, animator->board_euler[0] );
+ q_axis_angle( qpitch, (v3f){1.0f,0.0f,0.0f}, animator->board_euler[1] );
+ q_axis_angle( qroll, (v3f){0.0f,0.0f,1.0f}, animator->board_euler[2] );
+
+ q_mul( qyaw, qroll, qtrick );
+ q_mul( qpitch, qtrick, qtrick );
+ q_mul( kf_board->q, qtrick, kf_board->q );
+ q_normalize( kf_board->q );
+
+ kf_foot_l->co[2] = vg_lerpf( kf_foot_l->co[2], animator->foot_offset[0],
+ 0.5f * animator->weight );
+ kf_foot_r->co[2] = vg_lerpf( kf_foot_r->co[2], animator->foot_offset[1],
+ -0.5f * animator->weight );
+
+ kf_foot_l->co[1] += animator->slap;
+ kf_foot_r->co[1] += animator->slap;
+ kf_knee_l->co[1] += animator->slap;
+ kf_knee_r->co[1] += animator->slap;
+ kf_board->co[1] += animator->slap * animator->subslap;
+ kf_hip->co[1] += animator->slap * 0.25f;
+
+ /* kickflip and shuvit are in the wrong order for some reason */
+ if( animator->trick_type == k_trick_type_kickflip ){
+ kf_foot_l->co[0] += animator->trick_foot * 0.15f;
+ kf_foot_r->co[0] -= animator->trick_foot * 0.15f;
+ kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
+ kf_foot_r->co[1] -= animator->trick_foot * 0.18f;
+ }
+ else if( animator->trick_type == k_trick_type_shuvit ){
+ kf_foot_l->co[0] += animator->trick_foot * 0.2f;
+ kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
+ kf_foot_r->co[0] -= animator->trick_foot * 0.1f;
+ kf_foot_r->co[1] += animator->trick_foot * 0.09f;
+ }
+ else if( animator->trick_type == k_trick_type_treflip ){
+ kf_foot_l->co[0] += animator->trick_foot * 0.2f;
+ kf_foot_r->co[0] -= animator->trick_foot * 0.15f;
+ kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
+ kf_foot_r->co[1] -= animator->trick_foot * 0.18f;
+ }
+
+ /*
+ * animation wishlist:
+ * boardslide/grind jump animations
+ * when tricking the slap should not appply or less apply
+ * not animations however DONT target grinds that are vertically down.
+ */
+
+ /* truck rotation */
+ for( int i=0; i<2; i++ ){
+ float a = vg_minf( player_skate.truckv0[i][0], 1.0f );
+ a = -acosf( a ) * vg_signf( player_skate.truckv0[i][1] );
+
+ v4f q;
+ q_axis_angle( q, (v3f){0.0f,0.0f,1.0f}, a );
+ q_mul( q, kf_wheels[i]->q, kf_wheels[i]->q );
+ q_normalize( kf_wheels[i]->q );
+ }
+
+#if 1
+ {
+ mdl_keyframe
+ *kf_head = &pose->keyframes[localplayer.id_head-1],
+ *kf_elbow_l = &pose->keyframes[localplayer.id_ik_elbow_l-1],
+ *kf_elbow_r = &pose->keyframes[localplayer.id_ik_elbow_r-1],
+ *kf_hand_l = &pose->keyframes[localplayer.id_ik_hand_l-1],
+ *kf_hand_r = &pose->keyframes[localplayer.id_ik_hand_r-1],
+ *kf_hip = &pose->keyframes[localplayer.id_hip-1];
+
+ float warble = vg_perlin_fract_1d( vg.time, 2.0f, 2, 300 );
+ warble *= vg_maxf(animator->grind, fabsf(animator->weight)) * 0.3f;
+
+ v4f qrot;
+ q_axis_angle( qrot, (v3f){0.8f,0.7f,0.6f}, warble );
+
+ v3f origin = {0.0f,0.2f,0.0f};
+ keyframe_rotate_around( kf_hand_l, origin,
+ sk->bones[localplayer.id_ik_hand_l].co, qrot );
+ keyframe_rotate_around( kf_hand_r, origin,
+ sk->bones[localplayer.id_ik_hand_r].co, qrot );
+ keyframe_rotate_around( kf_hip, origin,
+ sk->bones[localplayer.id_hip].co, qrot );
+ keyframe_rotate_around( kf_elbow_r, origin,
+ sk->bones[localplayer.id_ik_elbow_r].co, qrot );
+ keyframe_rotate_around( kf_elbow_l, origin,
+ sk->bones[localplayer.id_ik_elbow_l].co, qrot );
+
+ q_inv( qrot, qrot );
+ q_mul( qrot, kf_head->q, kf_head->q );
+ q_normalize( kf_head->q );
+
+
+ /* hand placement */
+
+ u32 hand_id = animator->z < 0.5f?
+ localplayer.id_ik_hand_l: localplayer.id_ik_hand_r;
+
+ v3f sample_co;
+ m4x3f mmdl;
+ q_m3x3( pose->root_q, mmdl );
+ q_mulv( pose->root_q, pose->keyframes[hand_id-1].co, mmdl[3] );
+ v3_add( mmdl[3], pose->root_co, mmdl[3] );
+ m4x3_mulv( mmdl, sk->bones[hand_id].co, sample_co );
+
+ v3_muladds( sample_co, mmdl[1], 0.3f, sample_co );
+ vg_line_point( sample_co, 0.04f, 0xff0000ff );
+
+ v3f dir;
+ v3_muls( mmdl[1], -1.0f, dir );
+ ray_hit hit = { .dist = 1.5f };
+ if(ray_world( world_current_instance(), sample_co, dir, &hit, 0 )){
+ vg_line_cross( hit.pos, 0xff0000ff, 0.05f );
+ vg_line( sample_co, hit.pos, 0xffffffff );
+
+ f32 amt = vg_maxf( 0.0f, animator->slide-0.5f ) *
+ 2.0f * fabsf(animator->z*2.0f-1.0f);
+
+ f32 d = (hit.dist - 0.3f) * amt;
+ pose->keyframes[hand_id-1].co[1] -= d;
+ kf_hip->co[1] -= d*0.4f;
+ }
+
+ /* skid */
+ f32 amt = vg_maxf(0.0f, (animator->slide - 0.5f) * 2.0f);
+ u8 skidders[] = { localplayer.id_ik_foot_l,
+ localplayer.id_ik_foot_r,
+ localplayer.id_board };
+ v4f qskid;
+ q_axis_angle( qskid, (v3f){0,1,0}, -animator->steer[1]*0.2f );
+
+ for( u32 i=0; i<vg_list_size(skidders); i ++ ){
+ mdl_keyframe *kf = &pose->keyframes[ skidders[i]-1 ];
+ keyframe_rotate_around( kf,
+ (v3f){0,0,0.4f*(animator->z*2.0f-1.0f)*amt},
+ sk->bones[skidders[i]].co, qskid );
+ }
+ }
+#endif
+}
+
+void player__skate_effects( void *_animator, m4x3f *final_mtx,
+ struct player_board *board,
+ struct player_effects_data *effect_data ){
+ struct skeleton *sk = &localplayer.skeleton;
+ struct player_skate_animator *animator = _animator;
+
+ v3f vp0, vp1, vpc;
+ if( board ){
+ v3_copy((v3f){0.0f,0.02f, board->truck_positions[0][2]}, vp1 );
+ v3_copy((v3f){0.0f,0.02f, board->truck_positions[1][2]}, vp0 );
+ }
+ else{
+ v3_zero( vp0 );
+ v3_zero( vp1 );
+ }
+
+ v3f *board_mtx = final_mtx[ localplayer.id_board ];
+ m4x3_mulv( board_mtx, vp0, vp0 );
+ m4x3_mulv( board_mtx, vp1, vp1 );
+ v3_add( vp0, vp1, vpc );
+ v3_muls( vpc, 0.5f, vpc );
+
+ if( animator->surface == k_surface_prop_sand ){
+ if( (animator->slide>0.4f) && (v3_length2(animator->root_v)>4.0f*4.0f) ){
+ v3f v, co;
+ v3_muls( animator->root_v, 0.5f, v );
+ v3_lerp( vp0, vp1, vg_randf64(&vg.rand), co );
+
+ effect_data->sand.colour = 0xff8ec4e6;
+ effect_spark_apply( &effect_data->sand, co, v, vg.time_delta * 8.0 );
+ }
+ }
+
+ if( animator->grind > 0.5f ){
+ int back = 0, front = 0, mid = 0;
+
+ if( animator->activity == k_skate_activity_grind_5050 ){
+ back = 1;
+ front = 1;
+ }
+ else if( animator->activity == k_skate_activity_grind_back50 ){
+ back = 1;
+ }
+ else if( animator->activity == k_skate_activity_grind_front50 ){
+ front = 1;
+ }
+ else if( animator->activity == k_skate_activity_grind_boardslide ){
+ mid = 1;
+ }
+
+ if( back ){
+ effect_spark_apply( &effect_data->spark, vp0,
+ animator->root_v, vg.time_delta );
+ }
+
+ if( front ){
+ effect_spark_apply( &effect_data->spark, vp1,
+ animator->root_v, vg.time_delta );
+ }
+
+ if( mid ){
+ effect_spark_apply( &effect_data->spark, vpc,
+ animator->root_v, vg.time_delta );
+ }
+ }
+}
+
+void player__skate_post_animate(void){
+ struct player_skate_state *state = &player_skate.state;
+ localplayer.cam_velocity_influence = 1.0f;
+ localplayer.cam_dist = 1.8f;
+
+ v3f head = { 0.0f, 1.8f, 0.0f };
+ m4x3_mulv( localplayer.final_mtx[ localplayer.id_head ],
+ head, state->head_position );
+ m4x3_mulv( localplayer.rb.to_local,
+ state->head_position, state->head_position );