the never ending refactor
[carveJwlIkooP6JGAAIwe30JlM.git] / player_device_skate.h
diff --git a/player_device_skate.h b/player_device_skate.h
deleted file mode 100644 (file)
index d657b4a..0000000
+++ /dev/null
@@ -1,1769 +0,0 @@
-#ifndef PLAYER_DEVICE_SKATE_H
-#define PLAYER_DEVICE_SKATE_H
-
-#include "player_interface.h"
-#include "skeleton.h"
-#include "player_model.h"
-#include "player_device_common.h"
-
-VG_STATIC
-struct player_device_skate
-{
-   struct
-   {
-      enum skate_activity
-      {
-         k_skate_activity_air,
-         k_skate_activity_ground,
-         k_skate_activity_grind
-      }
-      activity,
-      activity_prev;
-
-      float steery,
-            steerx,
-            steery_s,
-            steerx_s,
-            reverse,
-            slip;
-
-      v3f   flip_axis;
-      float flip_time,
-            flip_rate;
-
-      m3x3f velocity_bias,
-            velocity_bias_pstep;
-      v3f apex;
-
-      int lift_frames;
-
-      v3f throw_v;
-      v3f cog_v, cog;
-
-      float grabbing;
-      v2f grab_mouse_delta;
-
-      int charging_jump, jump_dir;
-      float jump_charge;
-      double jump_time;
-
-      double start_push,
-             cur_push;
-
-      v3f prev_pos;
-
-      /* FIXME: Sensible names */
-      v3f vl,          /* 1st */
-          posl, dirl;  /* 3rd */
-   }
-   state,
-   state_gate_storage;
-
-   struct land_prediction
-   {
-      v3f   log[50];
-      v3f   n;
-      v3f   apex;
-      u32   log_length;
-      float score,
-            land_dist;
-
-      enum prediction_type
-      {
-         k_prediction_none,
-         k_prediction_land,
-         k_prediction_grind
-      }
-      type;
-
-      u32   colour;
-   }
-   predictions[22];
-   u32 prediction_count;
-   float land_dist;
-   v3f land_normal;
-
-   /* animation */
-   struct skeleton_anim *anim_stand, *anim_highg, *anim_slide,
-                        *anim_air,
-                        *anim_push,  *anim_push_reverse,
-                        *anim_ollie, *anim_ollie_reverse,
-                        *anim_grabs, *anim_stop;
-   rb_sphere sphere_front, sphere_back;
-   v3f board_offset;
-   v4f board_rotation;
-
-   float blend_slide,
-         blend_z,
-         blend_x,
-         blend_fly,
-         blend_stand,
-         blend_push,
-         blend_jump,
-         blend_airdir;
-
-   v2f wobble;
-
-   float debug_normal_pressure;
-   u32 device_id_walk;
-}
-localplayer_device_skate;
-
-VG_STATIC void player_skate_bind( player_device *dev,
-                                  player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-   struct skeleton *sk = &av->sk;
-
-   rb_update_transform( &player->rb );
-   s->anim_stand           = skeleton_get_anim( sk, "pose_stand" );
-   s->anim_highg           = skeleton_get_anim( sk, "pose_highg" );
-   s->anim_air             = skeleton_get_anim( sk, "pose_air" );
-   s->anim_slide           = skeleton_get_anim( sk, "pose_slide" );
-   s->anim_push            = skeleton_get_anim( sk, "push" );
-   s->anim_push_reverse    = skeleton_get_anim( sk, "push_reverse" );
-   s->anim_ollie           = skeleton_get_anim( sk, "ollie" );
-   s->anim_ollie_reverse   = skeleton_get_anim( sk, "ollie_reverse" );
-   s->anim_grabs           = skeleton_get_anim( sk, "grabs" );
-
-   s->device_id_walk = player_get_device( player, "walk" );
-}
-
-/* 
- * Collision detection routines
- *
- *
- */
-
-/*
- * Does collision detection on a sphere vs world, and applies some smoothing
- * filters to the manifold afterwards
- */
-VG_STATIC int skate_collide_smooth( player_interface *player,
-                                    m4x3f mtx, rb_sphere *sphere,
-                                    rb_ct *man )
-{
-   debug_sphere( mtx, sphere->radius, VG__BLACK );
-
-   int len = 0;
-   len = rb_sphere__scene( mtx, sphere, NULL, &world.rb_geo.inf.scene, man );
-
-   for( int i=0; i<len; i++ )
-   {
-      man[i].rba = &player->rb;
-      man[i].rbb = NULL;
-   }
-
-   rb_manifold_filter_coplanar( man, len, 0.05f );
-
-   if( len > 1 )
-   {
-      rb_manifold_filter_backface( man, len );
-      rb_manifold_filter_joint_edges( man, len, 0.05f );
-      rb_manifold_filter_pairs( man, len, 0.05f );
-   }
-   int new_len = rb_manifold_apply_filtered( man, len );
-   if( len && !new_len )
-      len = 1;
-   else
-      len = new_len;
-
-   return len;
-}
-/*
- * Gets the closest grindable edge to the player within max_dist 
- */
-VG_STATIC struct grind_edge *skate_collect_grind_edge( v3f p0, v3f p1,
-                                                       v3f c0, v3f c1, 
-                                                       float max_dist )
-{
-   bh_iter it;
-   bh_iter_init( 0, &it );
-
-   boxf region;
-
-   box_init_inf( region );
-   box_addpt( region, p0 );
-   box_addpt( region, p1 );
-   
-   float k_r = max_dist;
-   v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] );
-   v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] );
-
-   float closest = k_r*k_r;
-   struct grind_edge *closest_edge = NULL;
-   
-   int idx;
-   while( bh_next( world.grind_bh, &it, region, &idx ) )
-   {
-      struct grind_edge *edge = &world.grind_edges[ idx ];
-
-      float s,t;
-      v3f pa, pb;
-
-      float d2 = 
-         closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb );
-
-      if( d2 < closest )
-      {
-         closest = d2;
-         closest_edge = edge;
-         v3_copy( pa, c0 );
-         v3_copy( pb, c1 );
-      }
-   }
-
-   return closest_edge;
-}
-
-VG_STATIC int skate_grind_collide( player_device *dev,
-                                   player_interface *player, 
-                                   rb_ct *contact )
-{
-   v3f p0, p1, c0, c1;
-   v3_muladds( player->rb.co, player->rb.to_world[2],  0.5f, p0 );
-   v3_muladds( player->rb.co, player->rb.to_world[2], -0.5f, p1 );
-   v3_muladds( p0, player->rb.to_world[1], 0.125f-0.15f, p0 );
-   v3_muladds( p1, player->rb.to_world[1], 0.125f-0.15f, p1 );
-
-   float const k_r = 0.25f;
-   struct grind_edge *closest_edge = skate_collect_grind_edge( p0, p1, 
-                                                               c0, c1, k_r );
-
-   if( closest_edge )
-   {
-      v3f delta;
-      v3_sub( c1, c0, delta );
-
-      if( v3_dot( delta, player->rb.to_world[1] ) > 0.0001f )
-      {
-         contact->p = v3_length( delta );
-         contact->type = k_contact_type_edge;
-         contact->element_id = 0;
-         v3_copy( c1, contact->co );
-         contact->rba = NULL;
-         contact->rbb = NULL;
-
-         v3f edge_dir, axis_dir;
-         v3_sub( closest_edge->p1, closest_edge->p0, edge_dir );
-         v3_normalize( edge_dir );
-         v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
-         v3_cross( edge_dir, axis_dir, contact->n );
-
-         return 1;
-      }
-      else
-         return 0;
-   }
-
-   return 0;
-}
-
-/*
- *
- * Prediction system
- *
- *
- */
-
-/* 
- * Trace a path given a velocity rotation.
- *
- * TODO: this MIGHT be worth doing RK4 on the gravity field.
- */
-VG_STATIC void skate_score_biased_path( v3f co, v3f v, m3x3f vr, 
-                                        struct land_prediction *prediction )
-{
-   float pstep  = VG_TIMESTEP_FIXED * 10.0f;
-   float k_bias = 0.96f;
-
-   v3f pco, pco1, pv;
-   v3_copy( co, pco );
-   v3_muls( v,  k_bias, pv );
-
-   m3x3_mulv( vr, pv, pv );
-   v3_muladds( pco, pv, pstep, pco );
-
-   struct grind_edge *best_grind = NULL;
-   float closest_grind = INFINITY;
-
-   float grind_score    = INFINITY,
-         air_score      = INFINITY,
-         time_to_impact = 0.0f;
-
-   prediction->log_length = 0;
-   v3_copy( pco, prediction->apex );
-
-   for( int i=0; i<vg_list_size(prediction->log); i++ )
-   {
-      v3_copy( pco, pco1 );
-
-      pv[1] += -k_gravity * pstep;
-
-      m3x3_mulv( vr, pv, pv );
-      v3_muladds( pco, pv, pstep, pco );
-
-      if( pco[1] > prediction->apex[1] )
-         v3_copy( pco, prediction->apex );
-      
-      v3f vdir;
-
-      v3_sub( pco, pco1, vdir );
-
-      float l = v3_length( vdir );
-      v3_muls( vdir, 1.0f/l, vdir );
-
-      v3f c0, c1;
-      struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
-                                                        c0, c1, 0.4f );
-
-      if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
-      {
-         float d2 = v3_dist2( c0, c1 );
-         if( d2 < closest_grind )
-         {
-            closest_grind = d2;
-            best_grind = ge;
-            grind_score = closest_grind * 0.05f;
-         }
-      }
-
-      v3f n1;
-
-      float t1;
-      int idx = spherecast_world( pco1, pco, 0.4f, &t1, n1 );
-      if( idx != -1 )
-      {
-         v3_copy( n1, prediction->n );
-         air_score = -v3_dot( pv, n1 );
-         
-         u32 vert_index = world.scene_geo->arrindices[ idx*3 ];
-         struct world_material *mat = world_tri_index_material( vert_index );
-
-         /* Bias prediction towords ramps */
-         if( mat->info.flags & k_material_flag_skate_surface )
-            air_score *= 0.1f;
-
-         v3_lerp( pco1, pco, t1, prediction->log[ prediction->log_length ++ ] ); 
-         time_to_impact += t1 * pstep;
-         break;
-      }
-
-      time_to_impact += pstep;
-      v3_copy( pco, prediction->log[ prediction->log_length ++ ] );
-   }
-
-   if( grind_score < air_score )
-   {
-      prediction->score = grind_score; 
-      prediction->type = k_prediction_grind;
-   }
-   else if( air_score < INFINITY )
-   {
-      prediction->score = air_score;
-      prediction->type = k_prediction_land;
-   }
-   else
-   {
-      prediction->score = INFINITY;
-      prediction->type = k_prediction_none;
-   }
-
-   prediction->land_dist = time_to_impact;
-}
-
-VG_STATIC 
-void player_approximate_best_trajectory( player_interface *player,
-                                         struct player_device_skate *s )
-{
-   float pstep = VG_TIMESTEP_FIXED * 10.0f;
-   float best_velocity_delta = -9999.9f;
-
-   v3f axis;
-   v3_cross( player->rb.to_world[1], player->rb.v, axis );
-   v3_normalize( axis );
-
-   s->prediction_count = 0;
-   m3x3_identity( s->state.velocity_bias );
-
-   float best_vmod   = 0.0f,
-         min_score   =  INFINITY,
-         max_score   = -INFINITY;
-
-   v3_zero( s->state.apex );
-   s->land_dist = 0.0f;
-
-   /*
-    * Search a broad selection of futures
-    */
-   for( int m=-3;m<=12; m++ )
-   {
-      struct land_prediction *p = &s->predictions[ s->prediction_count ++ ];
-
-      float vmod = ((float)m / 15.0f)*0.09f;
-
-      m3x3f bias;
-      v4f bias_q;
-
-      q_axis_angle( bias_q, axis, vmod );
-      q_m3x3( bias_q, bias );
-
-      skate_score_biased_path( player->rb.co, player->rb.v, bias, p );
-
-      if( p->type != k_prediction_none )
-      {
-         if( p->score < min_score )
-         {
-            min_score = p->score;
-            best_vmod = vmod;
-            s->land_dist = p->land_dist;
-            v3_copy( p->apex, s->state.apex );
-         }
-
-         if( p->score > max_score )
-            max_score = p->score;
-      }
-   }
-
-   v4f vr_q;
-   q_axis_angle( vr_q, axis, best_vmod*0.1f );
-   q_m3x3( vr_q, s->state.velocity_bias );
-
-   q_axis_angle( vr_q, axis, best_vmod );
-   q_m3x3( vr_q, s->state.velocity_bias_pstep );
-
-   /*
-    * Logging
-    */
-   for( int i=0; i<s->prediction_count; i ++ )
-   {
-      struct land_prediction *p = &s->predictions[i];
-
-      float l = p->score;
-
-      if( l < 0.0f )
-      {
-         vg_error( "negative score! (%f)\n", l );
-      }
-
-      l -= min_score;
-      l /= (max_score-min_score);
-      l  = 1.0f - l;
-      l *= 255.0f;
-
-      p->colour = l;
-      p->colour <<= 8;
-      p->colour |= 0xff000000;
-   }
-
-
-   v2f steer = { player->input_js1h->axis.value,
-                 player->input_js1v->axis.value };
-   v2_normalize_clamp( steer );
-
-   if( (fabsf(steer[1]) > 0.5f) && (s->land_dist >= 1.0f) )
-   {
-      s->state.flip_rate = (1.0f/s->land_dist) * vg_signf(steer[1]) *
-                              s->state.reverse ;
-      s->state.flip_time = 0.0f;
-      v3_copy( player->rb.to_world[0], s->state.flip_axis );
-   }
-   else
-   {
-      s->state.flip_rate = 0.0f;
-      v3_zero( s->state.flip_axis );
-   }
-}
-
-/*
- *
- * Varius physics models
- * ------------------------------------------------
- */
-
-VG_STATIC void skate_apply_grind_model( player_interface *player,
-                                        struct player_device_skate *s,
-                                        rb_ct *manifold, int len )
-{
-   /* FIXME: Queue audio events instead */
-   if( len == 0 )
-   {
-      if( s->state.activity == k_skate_activity_grind )
-      {
-#if 0
-         audio_lock();
-         audio_player_set_flags( &audio_player_extra, 
-                                 AUDIO_FLAG_SPACIAL_3D );
-         audio_player_set_position( &audio_player_extra, player.rb.co );
-         audio_player_set_vol( &audio_player_extra, 20.0f );
-         audio_player_playclip( &audio_player_extra, &audio_board[6] );
-         audio_unlock();
-#endif
-
-         s->state.activity = k_skate_activity_air;
-      }
-      return;
-   }
-
-   v2f steer = { player->input_js1h->axis.value,
-                 player->input_js1v->axis.value };
-   v2_normalize_clamp( steer );
-
-   s->state.steery -= steer[0] * k_steer_air * k_rb_delta;
-   s->state.steerx += steer[1] * s->state.reverse * k_steer_air * k_rb_delta;
-   
-#if 0
-   v4f rotate;
-   q_axis_angle( rotate, player->rb.to_world[0], siX );
-   q_mul( rotate, player.rb.q, player.rb.q );
-#endif
-
-   s->state.slip = 0.0f;
-   s->state.activity = k_skate_activity_grind;
-
-   /* TODO: Compression */
-   v3f up = { 0.0f, 1.0f, 0.0f };
-   float angle = v3_dot( player->rb.to_world[1], up );
-
-   if( fabsf(angle) < 0.99f )
-   {
-      v3f axis; 
-      v3_cross( player->rb.to_world[1], up, axis );
-
-      v4f correction;
-      q_axis_angle( correction, axis, k_rb_delta * 10.0f * acosf(angle) );
-      q_mul( correction, player->rb.q, player->rb.q );
-   }
-
-   float const DOWNFORCE = -k_downforce*1.2f*VG_TIMESTEP_FIXED;
-   v3_muladds( player->rb.v, manifold->n, DOWNFORCE, player->rb.v );
-   m3x3_identity( s->state.velocity_bias );
-   m3x3_identity( s->state.velocity_bias_pstep );
-
-   if( s->state.activity_prev != k_skate_activity_grind )
-   {
-      /* FIXME: Queue audio events instead */
-#if 0
-      audio_lock();
-      audio_player_set_flags( &audio_player_extra, 
-                              AUDIO_FLAG_SPACIAL_3D );
-      audio_player_set_position( &audio_player_extra, player.rb.co );
-      audio_player_set_vol( &audio_player_extra, 20.0f );
-      audio_player_playclip( &audio_player_extra, &audio_board[5] );
-      audio_unlock();
-#endif
-   }
-}
-
-/*
- * Air control, no real physics
- */
-VG_STATIC void skate_apply_air_model( player_interface *player,
-                                      struct player_device_skate *s )
-{
-   if( s->state.activity != k_skate_activity_air )
-      return;
-
-   if( s->state.activity_prev != k_skate_activity_air )
-      player_approximate_best_trajectory( player, s );
-
-   m3x3_mulv( s->state.velocity_bias, player->rb.v, player->rb.v );
-   ray_hit hit;
-
-   /* 
-    * Prediction 
-    */
-   float pstep  = VG_TIMESTEP_FIXED * 1.0f;
-   float k_bias = 0.98f;
-
-   v3f pco, pco1, pv;
-   v3_copy( player->rb.co, pco );
-   v3_muls( player->rb.v, 1.0f, pv );
-   
-   float time_to_impact = 0.0f;
-   float limiter = 1.0f;
-
-   struct grind_edge *best_grind = NULL;
-   float closest_grind = INFINITY;
-
-   v3f target_normal = { 0.0f, 1.0f, 0.0f };
-   int has_target = 0;
-
-   for( int i=0; i<250; i++ )
-   {
-      v3_copy( pco, pco1 );
-      m3x3_mulv( s->state.velocity_bias, pv, pv );
-
-      pv[1] += -k_gravity * pstep;
-      v3_muladds( pco, pv, pstep, pco );
-      
-      ray_hit contact;
-      v3f vdir;
-
-      v3_sub( pco, pco1, vdir );
-      contact.dist = v3_length( vdir );
-      v3_divs( vdir, contact.dist, vdir);
-
-      v3f c0, c1;
-      struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
-                                                        c0, c1, 0.4f );
-
-      if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
-      {
-         vg_line( ge->p0, ge->p1, 0xff0000ff );
-         vg_line_cross( pco, 0xff0000ff, 0.25f );
-         has_target = 1;
-         break;
-      }
-      
-      float orig_dist = contact.dist;
-      if( ray_world( pco1, vdir, &contact ) )
-      {
-         v3_copy( contact.normal, target_normal );
-         has_target = 1;
-         time_to_impact += (contact.dist/orig_dist)*pstep;
-         vg_line_cross( contact.pos, 0xffff0000, 0.25f );
-         break;
-      }
-      time_to_impact += pstep;
-   }
-
-   if( has_target )
-   {
-      float angle = v3_dot( player->rb.to_world[1], target_normal );
-      v3f axis; 
-      v3_cross( player->rb.to_world[1], target_normal, axis );
-
-      limiter = vg_minf( 5.0f, time_to_impact )/5.0f;
-      limiter = 1.0f-limiter;
-      limiter *= limiter;
-      limiter = 1.0f-limiter;
-
-      if( fabsf(angle) < 0.99f )
-      {
-         v4f correction;
-         q_axis_angle( correction, axis, 
-                        acosf(angle)*(1.0f-limiter)*2.0f*VG_TIMESTEP_FIXED );
-         q_mul( correction, player->rb.q, player->rb.q );
-      }
-   }
-
-   v2f steer = { player->input_js1h->axis.value,
-                 player->input_js1v->axis.value };
-   v2_normalize_clamp( steer );
-
-   s->state.steery -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
-   s->state.steerx += steer[1] * s->state.reverse * k_steer_air 
-                                                         * limiter * k_rb_delta;
-   s->land_dist = time_to_impact;
-   v3_copy( target_normal, s->land_normal );
-}
-
-VG_STATIC void skate_get_board_points( player_interface *player,
-                                       struct player_device_skate *s,
-                                       v3f front, v3f back )
-{
-   v3f pos_front = {0.0f,0.0f,-k_board_length},
-       pos_back  = {0.0f,0.0f, k_board_length};
-
-   m4x3_mulv( player->rb.to_world, pos_front, front );
-   m4x3_mulv( player->rb.to_world, pos_back,  back );
-}
-
-/*
- * Casts and pushes a sphere-spring model into the world
- */
-VG_STATIC int skate_simulate_spring( player_interface *player,
-                                     struct player_device_skate *s,
-                                     v3f pos )
-{
-   float mod      = 0.7f * player->input_grab->axis.value + 0.3f,
-         spring_k = mod * k_spring_force,
-         damp_k   = mod * k_spring_dampener,
-         disp_k   = 0.4f;
-
-   v3f start, end;
-   v3_copy( pos, start );
-   v3_muladds( pos, player->rb.to_world[1], -disp_k, end );
-
-   float t;
-   v3f n;
-   int hit_info = spherecast_world( start, end, 0.2f, &t, n );
-
-   if( hit_info != -1 )
-   {
-      v3f F, delta;
-      v3_sub( start, player->rb.co, delta );
-      
-      float displacement = vg_clampf( 1.0f-t, 0.0f, 1.0f ),
-            damp         = 
-         vg_maxf( 0.0f, v3_dot( player->rb.to_world[1], player->rb.v ) );
-
-      v3_muls( player->rb.to_world[1], displacement*spring_k*k_rb_delta -
-                                       damp*damp_k*k_rb_delta, F );
-
-      v3_muladds( player->rb.v, F, 1.0f, player->rb.v );
-      
-      /* Angular velocity */
-      v3f wa;
-      v3_cross( delta, F, wa );
-      v3_muladds( player->rb.w, wa, k_spring_angular, player->rb.w );
-
-      v3_lerp( start, end, t, pos );
-      return 1;
-   }
-   else
-   {
-      v3_copy( end, pos );
-      return 0;
-   }
-}
-
-
-/* 
- * Handles connection between the player and the ground
- */
-VG_STATIC void skate_apply_interface_model( player_interface *player,
-                                            struct player_device_skate *s,
-                                            rb_ct *manifold, int len )
-{
-   if( !((s->state.activity == k_skate_activity_ground) ||
-         (s->state.activity == k_skate_activity_air )) )
-      return;
-
-   if( s->state.activity == k_skate_activity_air )
-      s->debug_normal_pressure = 0.0f;
-   else
-      s->debug_normal_pressure = v3_dot( player->rb.to_world[1], player->rb.v );
-
-   /* springs */
-   v3f spring0, spring1;
-
-   skate_get_board_points( player, s, spring1, spring0 );
-   int spring_hit0 = 0, //skate_simulate_spring( player, s, spring0 ),
-       spring_hit1 = 0; //skate_simulate_spring( player, s, spring1 );
-
-   v3f animavg, animdelta;
-   v3_add( spring0, spring1, animavg );
-   v3_muls( animavg, 0.5f, animavg );
-
-   v3_sub( spring1, spring0, animdelta );
-   v3_normalize( animdelta );
-
-   m4x3_mulv( player->rb.to_local, animavg, s->board_offset );
-
-   float dx = -v3_dot( animdelta, player->rb.to_world[2] ),
-         dy =  v3_dot( animdelta, player->rb.to_world[1] );
-
-   float angle = -atan2f( dy, dx );
-   q_axis_angle( s->board_rotation, (v3f){1.0f,0.0f,0.0f}, angle );
-
-   int lift_frames_limit = 6;
-
-   /* Surface connection */
-   if( len == 0 && !(spring_hit0 && spring_hit1) )
-   {
-      s->state.lift_frames ++;
-
-      if( s->state.lift_frames >= lift_frames_limit )
-         s->state.activity = k_skate_activity_air;
-   }
-   else
-   {
-      v3f surface_avg;
-      v3_zero( surface_avg );
-
-      for( int i=0; i<len; i++ )
-         v3_add( surface_avg, manifold[i].n, surface_avg );
-      v3_normalize( surface_avg );
-      
-      if( v3_dot( player->rb.v, surface_avg ) > 0.7f )
-      {
-         s->state.lift_frames ++;
-
-         if( s->state.lift_frames >= lift_frames_limit )
-            s->state.activity = k_skate_activity_air;
-      }
-      else
-      {
-         s->state.activity = k_skate_activity_ground;
-         s->state.lift_frames = 0;
-         v3f projected, axis;
-
-         float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
-         v3_muladds( player->rb.v, player->rb.to_world[1], 
-                     DOWNFORCE, player->rb.v );
-
-         float d = v3_dot( player->rb.to_world[2], surface_avg );
-         v3_muladds( surface_avg, player->rb.to_world[2], -d, projected );
-         v3_normalize( projected );
-
-         float angle = v3_dot( player->rb.to_world[1], projected );
-         v3_cross( player->rb.to_world[1], projected, axis );
-
-         if( fabsf(angle) < 0.9999f )
-         {
-            v4f correction;
-            q_axis_angle( correction, axis, 
-                          acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
-            q_mul( correction, player->rb.q, player->rb.q );
-         }
-      }
-   }
-}
-
-VG_STATIC void skate_apply_grab_model( player_interface *player,
-                                       struct player_device_skate *s )
-{
-   float grabt = player->input_grab->axis.value;
-
-   if( grabt > 0.5f )
-   {
-      v2_muladds( s->state.grab_mouse_delta, vg.mouse_delta, 0.02f, 
-                  s->state.grab_mouse_delta );
-
-      v2_normalize_clamp( s->state.grab_mouse_delta );
-   }
-   else
-      v2_zero( s->state.grab_mouse_delta );
-
-   s->state.grabbing = vg_lerpf( s->state.grabbing, grabt, 8.4f*k_rb_delta );
-}
-
-/*
- * Computes friction and surface interface model
- */
-VG_STATIC void skate_apply_friction_model( player_interface *player,
-                                           struct player_device_skate *s )
-{
-   if( s->state.activity != k_skate_activity_ground )
-      return;
-
-   /*
-    * Computing localized friction forces for controlling the character
-    * Friction across X is significantly more than Z
-    */
-
-   v3f vel;
-   m3x3_mulv( player->rb.to_local, player->rb.v, vel );
-   float slip = 0.0f;
-   
-   if( fabsf(vel[2]) > 0.01f )
-      slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
-
-   if( fabsf( slip ) > 1.2f )
-      slip = vg_signf( slip ) * 1.2f;
-
-   s->state.slip = slip;
-   s->state.reverse = -vg_signf(vel[2]);
-
-   vel[0] += vg_cfrictf( vel[0], k_friction_lat * k_rb_delta );
-   vel[2] += vg_cfrictf( vel[2], k_friction_resistance * k_rb_delta );
-
-   /* Pushing additive force */
-
-   if( !player->input_jump->button.value )
-   {
-      if( player->input_push->button.value )
-      {
-         if( (vg.time - s->state.cur_push) > 0.25 )
-            s->state.start_push = vg.time;
-
-         s->state.cur_push = vg.time;
-
-         double push_time = vg.time - s->state.start_push;
-
-         float cycle_time = push_time*k_push_cycle_rate,
-               accel      = k_push_accel * (sinf(cycle_time)*0.5f+0.5f),
-               amt        = accel * VG_TIMESTEP_FIXED,
-               current    = v3_length( vel ),
-               new_vel    = vg_minf( current + amt, k_max_push_speed ),
-               delta      = new_vel - vg_minf( current, k_max_push_speed );
-
-         vel[2] += delta * -s->state.reverse;
-      }
-   }
-
-   /* Send back to velocity */
-   m3x3_mulv( player->rb.to_world, vel, player->rb.v );
-   
-   /* Steering */
-   float input = player->input_js1h->axis.value,
-         grab  = player->input_grab->axis.value,
-         steer = input * (1.0f-(s->state.jump_charge+grab)*0.4f),
-         steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
-
-   s->state.steery -= steer_scaled * k_rb_delta;
-}
-
-VG_STATIC void skate_apply_jump_model( player_interface *player,
-                                       struct player_device_skate *s )
-{
-   int charging_jump_prev = s->state.charging_jump;
-   s->state.charging_jump = player->input_jump->button.value;
-
-   /* Cannot charge this in air */
-   if( s->state.activity != k_skate_activity_ground )
-      s->state.charging_jump = 0;
-
-   if( s->state.charging_jump )
-   {
-      s->state.jump_charge += k_rb_delta * k_jump_charge_speed;
-
-      if( !charging_jump_prev )
-         s->state.jump_dir = s->state.reverse>0.0f? 1: 0;
-   }
-   else
-   {
-      s->state.jump_charge -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
-   }
-
-   s->state.jump_charge = vg_clampf( s->state.jump_charge, 0.0f, 1.0f );
-
-   if( s->state.activity == k_skate_activity_air )
-      return;
-
-   /* player let go after charging past 0.2: trigger jump */
-   if( (!s->state.charging_jump) && (s->state.jump_charge > 0.2f) )
-   {
-      v3f jumpdir;
-      
-      /* Launch more up if alignment is up else improve velocity */
-      float aup = v3_dot( (v3f){0.0f,1.0f,0.0f}, player->rb.to_world[1] ),
-            mod = 0.5f,
-            dir = mod + fabsf(aup)*(1.0f-mod);
-
-      v3_copy( player->rb.v, jumpdir );
-      v3_normalize( jumpdir );
-      v3_muls( jumpdir, 1.0f-dir, jumpdir );
-      v3_muladds( jumpdir, player->rb.to_world[1], dir, jumpdir );
-      v3_normalize( jumpdir );
-      
-      float force = k_jump_force*s->state.jump_charge;
-      v3_muladds( player->rb.v, jumpdir, force, player->rb.v );
-      s->state.jump_charge = 0.0f;
-
-      s->state.jump_time = vg.time;
-
-      v2f steer = { player->input_js1h->axis.value,
-                    player->input_js1v->axis.value };
-      v2_normalize_clamp( steer );
-
-      float maxspin = k_steer_air * k_rb_delta * k_spin_boost;
-      s->state.steery_s = -steer[0] * maxspin;
-      s->state.steerx = s->state.steerx_s;
-
-      /* FIXME audio events */
-#if 0
-      audio_lock();
-      audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
-      audio_player_set_position( &audio_player_extra, player.rb.co );
-      audio_player_set_vol( &audio_player_extra, 20.0f );
-      audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
-      audio_unlock();
-#endif
-   }
-}
-
-VG_STATIC void skate_apply_pump_model( player_interface *player,
-                                       struct player_device_skate *s )
-{
-   /* Throw / collect routine 
-    *
-    * TODO: Max speed boost
-    */
-   if( player->input_grab->axis.value > 0.5f )
-   {
-      if( s->state.activity == k_skate_activity_ground )
-      {
-         /* Throw */
-         v3_muls( player->rb.to_world[1], k_mmthrow_scale, s->state.throw_v );
-      }
-   }
-   else
-   {
-      /* Collect */
-      float doty = v3_dot( player->rb.to_world[1], s->state.throw_v );
-      
-      v3f Fl, Fv;
-      v3_muladds( s->state.throw_v, player->rb.to_world[1], -doty, Fl);
-
-      if( s->state.activity == k_skate_activity_ground )
-      {
-         v3_muladds( player->rb.v,     Fl,  k_mmcollect_lat, player->rb.v );
-         v3_muladds( s->state.throw_v, Fl, -k_mmcollect_lat, s->state.throw_v );
-      }
-
-      v3_muls( player->rb.to_world[1], -doty, Fv );
-      v3_muladds( player->rb.v,     Fv, k_mmcollect_vert, player->rb.v );
-      v3_muladds( s->state.throw_v, Fv, k_mmcollect_vert, s->state.throw_v );
-   }
-
-   /* Decay */
-   if( v3_length2( s->state.throw_v ) > 0.0001f )
-   {
-      v3f dir;
-      v3_copy( s->state.throw_v, dir );
-      v3_normalize( dir );
-
-      float max = v3_dot( dir, s->state.throw_v ),
-            amt = vg_minf( k_mmdecay * k_rb_delta, max );
-      v3_muladds( s->state.throw_v, dir, -amt, s->state.throw_v );
-   }
-}
-
-VG_STATIC void skate_apply_cog_model( player_interface *player,
-                                      struct player_device_skate *s )
-{
-   v3f ideal_cog, ideal_diff;
-   v3_muladds( player->rb.co, player->rb.to_world[1],
-               1.0f-player->input_grab->axis.value, ideal_cog );
-   v3_sub( ideal_cog, s->state.cog, ideal_diff );
-
-   /* Apply velocities */
-   v3f rv;
-   v3_sub( player->rb.v, s->state.cog_v, rv );
-
-   v3f F;
-   v3_muls( ideal_diff, -k_cog_spring * k_rb_rate, F );
-   v3_muladds( F, rv,   -k_cog_damp * k_rb_rate, F );
-
-   float ra = k_cog_mass_ratio,
-         rb = 1.0f-k_cog_mass_ratio;
-
-   /* Apply forces & intergrate */
-   v3_muladds( s->state.cog_v, F, -rb, s->state.cog_v );
-   s->state.cog_v[1] += -9.8f * k_rb_delta;
-   v3_muladds( s->state.cog, s->state.cog_v, k_rb_delta, s->state.cog );
-}
-
-VG_STATIC void skate_collision_response( player_interface *player,
-                                         struct player_device_skate *s,
-                                         rb_ct *manifold, int len )
-{
-   for( int j=0; j<10; j++ )
-   {
-      for( int i=0; i<len; i++ )
-      {
-         struct contact *ct = &manifold[i];
-         
-         v3f dv, delta;
-         v3_sub( ct->co, player->rb.co, delta ); 
-         v3_cross( player->rb.w, delta, dv );
-         v3_add( player->rb.v, dv, dv );
-
-         float vn = -v3_dot( dv, ct->n );
-         vn += ct->bias;
-
-         float temp = ct->norm_impulse;
-         ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
-         vn = ct->norm_impulse - temp;
-
-         v3f impulse;
-         v3_muls( ct->n, vn, impulse );
-
-         if( fabsf(v3_dot( impulse, player->rb.to_world[2] )) > 10.0f ||
-             fabsf(v3_dot( impulse, player->rb.to_world[1] )) > 50.0f )
-         {
-            /* FIXME */
-#if 0
-            player_kill();
-            return;
-#endif
-         }
-
-         v3_add( impulse, player->rb.v, player->rb.v );
-         v3_cross( delta, impulse, impulse );
-
-         /*
-          * W Impulses are limited to the Y and X axises, we don't really want
-          * roll angular velocities being included.
-          *
-          * Can also tweak the resistance of each axis here by scaling the wx,wy
-          * components.
-          */
-         
-         float wy = v3_dot( player->rb.to_world[1], impulse ) * 0.8f,
-               wx = v3_dot( player->rb.to_world[0], impulse ) * 1.0f;
-
-         v3_muladds( player->rb.w, player->rb.to_world[1], wy, player->rb.w );
-         v3_muladds( player->rb.w, player->rb.to_world[0], wx, player->rb.w );
-      }
-   }
-}
-
-VG_STATIC void skate_integrate( player_interface *player,
-                                struct player_device_skate *s )
-{
-   /* integrate rigidbody velocities */
-   v3f gravity = { 0.0f, -9.6f, 0.0f };
-   v3_muladds( player->rb.v, gravity, k_rb_delta, player->rb.v );
-   v3_muladds( player->rb.co, player->rb.v, k_rb_delta, player->rb.co );
-
-   float decay_rate = 0.5f*0.125f;
-
-   if( s->state.activity == k_skate_activity_air )
-   {
-      float dist = 1.0f-(s->land_dist/4.0f);
-      decay_rate = 0.5f * vg_maxf( dist*dist, 0.0f );
-   }
-
-   v3_lerp( player->rb.w, (v3f){0.0f,0.0f,0.0f}, decay_rate, player->rb.w );
-
-   if( v3_length2( player->rb.w ) > 0.0f )
-   {
-      v4f rotation;
-      v3f axis;
-      v3_copy( player->rb.w, axis );
-      
-      float mag = v3_length( axis );
-      v3_divs( axis, mag, axis );
-      q_axis_angle( rotation, axis, mag*k_rb_delta );
-      q_mul( rotation, player->rb.q, player->rb.q );
-   }
-
-   /* integrate steering velocities */
-   v4f rotate; 
-   float l = (s->state.activity == k_skate_activity_air)? 0.04f: 0.24f;
-
-   s->state.steery_s = vg_lerpf( s->state.steery_s, s->state.steery, l );
-   s->state.steerx_s = vg_lerpf( s->state.steerx_s, s->state.steerx, l );
-
-   q_axis_angle( rotate, player->rb.to_world[1], s->state.steery_s );
-   q_mul( rotate, player->rb.q, player->rb.q );
-
-   q_axis_angle( rotate, player->rb.to_world[0], s->state.steerx_s );
-   q_mul( rotate, player->rb.q, player->rb.q );
-
-   s->state.steerx = 0.0f;
-   s->state.steery = 0.0f;
-
-#if 0
-   v3_sub( player.rb.v, s->phys.v_prev, s->phys.a );
-   v3_muls( s->phys.a, 1.0f/VG_TIMESTEP_FIXED, s->phys.a );
-   v3_copy( player.rb.v, s->phys.v_prev );
-#endif
-
-   s->state.flip_time += s->state.flip_rate * k_rb_delta;
-   rb_update_transform( &player->rb );
-}
-
-VG_STATIC void player_skate_update( player_device *dev,
-                                    player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   v3_copy( player->rb.co, s->state.prev_pos );
-   s->state.activity_prev = s->state.activity;
-
-   /* Setup colliders */
-   m4x3f mtx_front, mtx_back;
-   m3x3_identity( mtx_front );
-   m3x3_identity( mtx_back );
-
-   skate_get_board_points( player, s, mtx_front[3], mtx_back[3] );
-
-   s->sphere_back.radius = 0.3f;
-   s->sphere_front.radius = 0.3f;
-
-   /* create manifold(s) */
-   rb_ct manifold[72],
-         *interface_manifold = NULL,
-         *grind_manifold = NULL;
-
-   int 
-   len_front = skate_collide_smooth( player, mtx_front, 
-                                     &s->sphere_front, manifold ),
-   len_back = skate_collide_smooth( player, mtx_back,  
-                                    &s->sphere_back, &manifold[len_front] ),
-   interface_len = len_front + len_back;
-
-   /* try to slap both wheels onto the ground when landing to prevent mega 
-    * angular velocities being added */
-   if( (s->state.activity == k_skate_activity_air) && (len_front != len_back) )
-   {
-      v3f trace_from, trace_dir;
-      v3_muls( player->rb.to_world[1], -1.0f, trace_dir );
-
-      if( len_front )
-         v3_copy( mtx_back[3],  trace_from );
-      else
-         v3_copy( mtx_front[3], trace_from );
-
-      ray_hit ray;
-      ray.dist = 0.6f;
-
-      if( ray_world( trace_from, trace_dir, &ray ) )
-      {
-         rb_ct *ct = &manifold[ interface_len ];
-
-         v3_copy( ray.pos, ct->co );
-         v3_copy( ray.normal, ct->n );
-         ct->p = 0.0f;
-
-         interface_len ++;
-      }
-   }
-
-   interface_manifold = manifold;
-   grind_manifold = manifold + interface_len;
-
-   int grind_len = skate_grind_collide( dev, player, grind_manifold );
-
-   for( int i=0; i<interface_len+grind_len; i ++ )
-   {
-      rb_prepare_contact( &manifold[i] );
-      rb_debug_contact( &manifold[i] );
-   }
-
-   skate_apply_grind_model( player, s, grind_manifold, grind_len );
-   skate_apply_interface_model( player, s, manifold, interface_len );
-   
-   skate_apply_pump_model( player, s );
-   skate_apply_cog_model( player, s );
-   skate_collision_response( player, s, manifold, interface_len + grind_len );
-
-   skate_apply_grab_model( player, s );
-   skate_apply_friction_model( player, s );
-   skate_apply_jump_model( player, s );
-   skate_apply_air_model( player, s );
-
-   skate_integrate( player, s );
-
-   vg_line_pt3( s->state.cog, 0.1f,  VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.11f, VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.12f, VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.13f, VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.14f, VG__WHITE );
-
-   vg_line( player->rb.co, s->state.cog, VG__RED );
-
-
-   teleport_gate *gate;
-   if( (gate = world_intersect_gates( player->rb.co, s->state.prev_pos )) )
-   {
-      m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
-      m3x3_mulv( gate->transport, player->rb.v,  player->rb.v );
-      m4x3_mulv( gate->transport, s->state.cog,   s->state.cog );
-      m3x3_mulv( gate->transport, s->state.cog_v, s->state.cog_v );
-      m3x3_mulv( gate->transport, s->state.throw_v, s->state.throw_v );
-
-      /*camera */
-      m4x3_mulv( gate->transport, s->state.posl, s->state.posl );
-      m3x3_mulv( gate->transport, s->state.vl, s->state.vl );
-      m3x3_mulv( gate->transport, s->state.dirl, s->state.dirl );
-
-#if 0
-      mixedcam_transport( &s->state.cam, gate );
-#endif
-
-      v4f transport_rotation;
-      m3x3_q( gate->transport, transport_rotation );
-      q_mul( transport_rotation, player->rb.q, player->rb.q );
-      rb_update_transform( &player->rb );
-
-      s->state_gate_storage = s->state;
-      player_pass_gate( player, gate );
-   }
-}
-
-VG_STATIC void player_skate_ui( player_device *dev, player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-
-   /* FIXME: Compression */
-   player_debugtext( 1, "V:  %5.2f %5.2f %5.2f",player->rb.v[0],
-                                                player->rb.v[1],
-                                                player->rb.v[2] );
-   player_debugtext( 1, "CO: %5.2f %5.2f %5.2f",player->rb.co[0],
-                                                player->rb.co[1],
-                                                player->rb.co[2] );
-   player_debugtext( 1, "W:  %5.2f %5.2f %5.2f",player->rb.w[0],
-                                                player->rb.w[1],
-                                                player->rb.w[2] );
-
-   player_debugtext( 1, "activity: %s\n",
-                           (const char *[]){ "k_skate_activity_air",
-                                             "k_skate_activity_ground",
-                                             "k_skate_activity_grind }" }
-                                             [s->state.activity] );
-   player_debugtext( 1, "steer_s: %5.2f %5.2f [%.2f %.2f]\n",
-                        s->state.steerx_s, s->state.steery_s,
-                        k_steer_ground, k_steer_air );
-   player_debugtext( 1, "flip: %.4f %.4f\n", s->state.flip_rate, 
-                                             s->state.flip_time );
-}
-
-VG_STATIC void player_skate_animate( player_device *dev, 
-                                     player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-   struct skeleton *sk = &av->sk;
-
-   /* Camera position */
-   /* TODO split up */
-   /* FIXME */
-
-#if 0
-   v3_muladds( phys->m, phys->a, VG_TIMESTEP_FIXED, phys->m );
-   v3_lerp( phys->m, (v3f){0.0f,0.0f,0.0f}, 0.1f, phys->m );
-
-   phys->m[0] = vg_clampf( phys->m[0], -2.0f, 2.0f );
-   phys->m[1] = vg_clampf( phys->m[1], -2.0f, 2.0f );
-   phys->m[2] = vg_clampf( phys->m[2], -2.0f, 2.0f );
-   v3_lerp( phys->bob, phys->m, 0.2f, phys->bob );
-#endif
-
-   /* Head */
-   float kheight = 2.0f,
-         kleg = 0.6f;
-
-   v3f offset;
-   v3_zero( offset );
-
-   m4x3_mulv( player->rb.to_local, s->state.cog, offset );
-   v3_muls( offset, -4.0f, offset );
-
-#if 0
-   m3x3_mulv( player.inv_visual_transform, phys->bob, offset );
-#endif
-
-   static float speed_wobble = 0.0f, speed_wobble_2 = 0.0f;
-
-   float curspeed  = v3_length( player->rb.v ),
-         kickspeed = vg_clampf( curspeed*(1.0f/40.0f), 0.0f, 1.0f ),
-         kicks     = (vg_randf()-0.5f)*2.0f*kickspeed,
-         sign      = vg_signf( kicks );
-
-   s->wobble[0] = vg_lerpf( s->wobble[0], kicks*kicks*sign, 6.0f*vg.time_delta);
-   s->wobble[1] = vg_lerpf( s->wobble[1], speed_wobble,     2.4f*vg.time_delta);
-
-   offset[0] *= 0.26f;
-   offset[0] += speed_wobble_2*3.0f;
-
-   offset[1] *= -0.3f;
-   offset[2] *= 0.01f;
-
-   offset[0]=vg_clampf(offset[0],-0.8f,0.8f)*(1.0f-fabsf(s->blend_slide)*0.9f);
-   offset[1]=vg_clampf(offset[1],-0.5f,0.0f);
-
-   /* 
-    * Animation blending
-    * ===========================================
-    */
-   
-   /* sliding */
-   {
-      float desired  = vg_clampf( fabsf( s->state.slip ), 0.0f, 1.0f );
-      s->blend_slide = vg_lerpf( s->blend_slide, desired, 2.4f*vg.time_delta);
-   }
-   
-   /* movement information */
-   {
-      int iair = (s->state.activity == k_skate_activity_air) ||
-                 (s->state.activity == k_skate_activity_grind );
-
-      float dirz = s->state.reverse > 0.0f? 0.0f: 1.0f,
-            dirx = s->state.slip < 0.0f?    0.0f: 1.0f,
-            fly  = iair?                    1.0f: 0.0f;
-
-      s->blend_z    = vg_lerpf( s->blend_z,   dirz, 2.4f*vg.time_delta );
-      s->blend_x    = vg_lerpf( s->blend_x,   dirx, 0.6f*vg.time_delta );
-      s->blend_fly  = vg_lerpf( s->blend_fly, fly,  2.4f*vg.time_delta );
-   }
-
-   mdl_keyframe apose[32], bpose[32];
-   mdl_keyframe ground_pose[32];
-   {
-      /* when the player is moving fast he will crouch down a little bit */
-      float stand = 1.0f - vg_clampf( curspeed * 0.03f, 0.0f, 1.0f );
-      s->blend_stand = vg_lerpf( s->blend_stand, stand, 6.0f*vg.time_delta );
-
-      /* stand/crouch */
-      float dir_frame   = s->blend_z * (15.0f/30.0f),
-            stand_blend = offset[1]*-2.0f;
-
-      v3f local_cog;
-      m4x3_mulv( player->rb.to_local, s->state.cog, local_cog );
-
-      stand_blend = vg_clampf( 1.0f-local_cog[1], 0, 1 );
-
-      skeleton_sample_anim( sk, s->anim_stand, dir_frame, apose );
-      skeleton_sample_anim( sk, s->anim_highg, dir_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
-
-      /* sliding */
-      float slide_frame = s->blend_x * (15.0f/30.0f);
-      skeleton_sample_anim( sk, s->anim_slide, slide_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, s->blend_slide, apose );
-
-      /* pushing */
-      double push_time = vg.time - s->state.start_push;
-      s->blend_push = vg_lerpf( s->blend_push,
-                               (vg.time - s->state.cur_push) < 0.125,
-                               6.0f*vg.time_delta );
-
-      float pt = push_time + vg.accumulator;
-      if( s->state.reverse > 0.0f )
-         skeleton_sample_anim( sk, s->anim_push, pt, bpose );
-      else
-         skeleton_sample_anim( sk, s->anim_push_reverse, pt, bpose );
-
-      skeleton_lerp_pose( sk, apose, bpose, s->blend_push, apose );
-
-      /* trick setup */
-      float jump_start_frame = 14.0f/30.0f;
-
-      float charge = s->state.jump_charge;
-      s->blend_jump = vg_lerpf( s->blend_jump, charge, 8.4f*vg.time_delta );
-
-      float setup_frame = charge * jump_start_frame,
-            setup_blend = vg_minf( s->blend_jump, 1.0f );
-      
-      float jump_frame = (vg.time - s->state.jump_time) + jump_start_frame;
-      if( jump_frame >= jump_start_frame && jump_frame <= (40.0f/30.0f) )
-         setup_frame = jump_frame;
-
-      struct skeleton_anim *jump_anim = s->state.jump_dir?
-                                        s->anim_ollie:
-                                        s->anim_ollie_reverse;
-
-      skeleton_sample_anim_clamped( sk, jump_anim, setup_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
-   }
-   
-   mdl_keyframe air_pose[32];
-   {
-      float target = -player->input_js1h->axis.value;
-      s->blend_airdir = vg_lerpf( s->blend_airdir, target, 2.4f*vg.time_delta );
-      
-      float air_frame = (s->blend_airdir*0.5f+0.5f) * (15.0f/30.0f);
-      skeleton_sample_anim( sk, s->anim_air, air_frame, apose );
-
-      static v2f grab_choice;
-
-      v2f grab_input = { player->input_js2h->axis.value,
-                         player->input_js2v->axis.value };
-      v2_add( s->state.grab_mouse_delta, grab_input, grab_input );
-      if( v2_length2( grab_input ) <= 0.001f )
-         grab_input[0] = -1.0f;
-      else
-         v2_normalize_clamp( grab_input );
-      v2_lerp( grab_choice, grab_input, 2.4f*vg.time_delta, grab_choice );
-
-      float ang = atan2f( grab_choice[0], grab_choice[1] ),
-            ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
-            grab_frame = ang_unit * (15.0f/30.0f);
-
-      skeleton_sample_anim( sk, s->anim_grabs, grab_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, s->state.grabbing, air_pose );
-   }
-
-   skeleton_lerp_pose( sk, ground_pose, air_pose, s->blend_fly, dev->pose );
-
-   float add_grab_mod = 1.0f - s->blend_fly;
-
-   /* additive effects */
-   {
-      u32 apply_to[] = { av->id_hip, 
-                         av->id_ik_hand_l,
-                         av->id_ik_hand_r,
-                         av->id_ik_elbow_l,
-                         av->id_ik_elbow_r };
-
-      for( int i=0; i<vg_list_size(apply_to); i ++ )
-      {
-         dev->pose[apply_to[i]-1].co[0] += offset[0]*add_grab_mod;
-         dev->pose[apply_to[i]-1].co[2] += offset[2]*add_grab_mod;
-      }
-
-      mdl_keyframe *kf_board  = &dev->pose[av->id_board-1],
-                   *kf_foot_l = &dev->pose[av->id_ik_foot_l-1],
-                   *kf_foot_r = &dev->pose[av->id_ik_foot_r-1];
-
-      v3f bo;
-      v3_muls( s->board_offset, add_grab_mod, bo );
-
-      v3_add( bo, kf_board->co,  kf_board->co );
-      v3_add( bo, kf_foot_l->co, kf_foot_l->co );
-      v3_add( bo, kf_foot_r->co, kf_foot_r->co );
-
-      m3x3f c;
-      q_m3x3( s->board_rotation, c );
-
-      v3f d;
-      v3_sub( kf_foot_l->co, bo, d );
-      m3x3_mulv( c, d, d );
-      v3_add( bo, d, kf_foot_l->co );
-      
-      v3_sub( kf_foot_r->co, bo, d );
-      m3x3_mulv( c, d, d );
-      v3_add( bo, d, kf_foot_r->co );
-
-      q_mul( s->board_rotation, kf_board->q, kf_board->q );
-      q_normalize( kf_board->q );
-   }
-
-   /* transform */
-   rb_extrapolate( &player->rb, dev->pose_root_co, dev->pose_root_q );
-
-   v3_muladds( dev->pose_root_co, player->rb.to_world[1], -0.28f, 
-               dev->pose_root_co );
-
-   v4f qresy, qresx, qresidual;
-   m3x3f mtx_residual;
-   float substep = vg_clampf( vg.accumulator / VG_TIMESTEP_FIXED, 0.0f, 1.0f );
-   q_axis_angle( qresy, player->rb.to_world[1], s->state.steery_s*substep );
-   q_axis_angle( qresx, player->rb.to_world[0], s->state.steerx_s*substep );
-
-   q_mul( qresy, qresx, qresidual );
-   q_normalize( qresidual );
-   q_mul( dev->pose_root_q, qresidual, dev->pose_root_q );
-   q_normalize( dev->pose_root_q );
-
-   v4f qflip;
-   if( (s->state.activity == k_skate_activity_air) &&
-       (fabsf(s->state.flip_rate) > 0.01f) )
-   {
-      float angle = vg_clampf( s->state.flip_time, -1.0f, 1.0f ) * VG_TAUf,
-            distm = s->land_dist * fabsf(s->state.flip_rate) * 3.0f,
-            blend = vg_clampf( 1.0f-distm, 0.0f, 1.0f );
-
-      angle = vg_lerpf( angle, vg_signf(s->state.flip_rate) * VG_TAUf, blend );
-
-      q_axis_angle( qflip, s->state.flip_axis, angle );
-      q_mul( qflip, dev->pose_root_q, dev->pose_root_q );
-      q_normalize( dev->pose_root_q );
-
-      v3f rotation_point, rco;
-      v3_muladds( player->rb.co, player->rb.to_world[1], 0.5f, rotation_point );
-      v3_sub( dev->pose_root_co, rotation_point, rco );
-      
-      /* FIXME: q_mul v3 */
-      m3x3f TEMP;
-      q_m3x3( qflip, TEMP );
-      m3x3_mulv( TEMP, rco, rco );
-      v3_add( rco, rotation_point, dev->pose_root_co );
-   }
-
-#if 0
-   if( cl_thirdperson )
-   {
-      if( !followcam_will_hit_gate( player, &s->state.cam ) )
-      {
-         m4x3f inverse;
-         m4x3_invert_affine( s->state.cam.gate->transport, inverse );
-         m4x3_mul( inverse, transform, transform );
-      }
-   }
-#endif
-}
-
-VG_STATIC void skate_camera_vector_look( camera *cam, v3f v, float C, float k )
-{
-   float yaw = atan2f( v[0], -v[2] ),
-       pitch = atan2f
-               ( 
-                   -v[1], 
-                   sqrtf
-                   (
-                     v[0]*v[0] + v[2]*v[2]
-                   )
-               ) * C + k;
-
-   cam->angles[0] = yaw;
-   cam->angles[1] = pitch;
-}
-
-VG_STATIC void skate_camera_firstperson( player_device *dev,
-                                         player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   /* FIXME: viewpoint entity */
-   v3f vp = {-0.1f,1.8f,0.0f};
-   m4x3_mulv( av->sk.final_mtx[ av->id_head-1 ], vp, dev->cam_1st.pos );
-
-   v3_zero( dev->cam_1st.angles );
-   dev->cam_1st.fov = 119.0f;
-
-   v3f flat_dir,
-       vel_dir,
-       look_dir;
-
-   v3_copy( player->rb.v, vel_dir );
-   //v3_normalize( vel_dir );
-
-   float tti = s->land_dist;
-   v3f   norm;
-   v3_copy( s->land_normal, norm );
-
-   if( s->state.activity == k_skate_activity_ground )
-   {
-      tti = 0.0f;
-      v3_copy( player->rb.to_world[1], norm );
-   }
-
-   v3_muladds( vel_dir, norm, -v3_dot(vel_dir,norm), flat_dir );
-   //v3_normalize( flat_dir );
-
-   v3_lerp( flat_dir, vel_dir, vg_clampf( tti / 2.0f, 0.4f, 1.0f ), look_dir );
-   v3_lerp( s->state.vl, look_dir, 4.0f*vg.time_delta, s->state.vl );
-
-   skate_camera_vector_look( &dev->cam_1st, s->state.vl, 1.0f, 0.25f );
-}
-
-VG_STATIC void skate_camera_thirdperson( player_device *dev,
-                                         player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   v3f origin, dir, target;
-   v3_copy( player->rb.co, origin );
-   v3_add( origin, (v3f){0.0f,1.35f,0.0f}, origin );
-   v3_sub( origin, s->state.posl, dir );
-   
-   if( v3_length2( dir ) < 0.1f*0.1f )
-      v3_copy( (v3f){ 0.0f, 0.0f, 1.0f }, dir );   /* FIXME */
-   else
-      v3_normalize( dir );
-
-   if( s->state.activity == k_skate_activity_air )
-      dir[1] *= vg_maxf( 0.0f, 1.0f - (s->land_dist/2.0f) );
-   dir[1] *= 0.0f;
-
-   v3_muladds( origin, dir, -2.0f, target );
-
-   v3_lerp( s->state.posl, target, vg.frame_delta * 15.0f, s->state.posl );
-   v3_lerp( s->state.dirl, dir, 18.0f*vg.time_delta, s->state.dirl );
-
-   v3_copy( s->state.posl, dev->cam_3rd.pos );
-   skate_camera_vector_look( &dev->cam_3rd, s->state.dirl, 1.0f, 0.2f );
-}
-
-VG_STATIC void player_skate_post_animate( player_device *dev,
-                                          player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   v3_zero( dev->cam_1st.pos );
-   v3_zero( dev->cam_1st.angles );
-   dev->cam_1st.fov = 90.0f;
-
-   skate_camera_thirdperson( dev, player );
-   skate_camera_firstperson( dev, player );
-
-   /* FIXME: Organize this. Its int wrong fucking place */
-   v3f vp0 = {0.0f,0.1f, 0.6f},
-       vp1 = {0.0f,0.1f,-0.6f};
-
-   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp0, TEMP_BOARD_0 );
-   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp1, TEMP_BOARD_1 );
-}
-
-VG_STATIC void player_skate_reset( player_device *dev,
-                                   player_interface *player,
-                                   struct respawn_point *rp )
-{
-   struct player_device_skate *s = dev->storage;
-   v3_muladds( player->rb.co, player->rb.to_world[1], 1.0f, s->state.cog );
-
-#if 0
-   mixedcam_reset( player, &s->state.cam );
-#endif
-}
-
-VG_STATIC int player_skate_event( player_device *dev, player_interface *player,
-                                  enum player_device_event_type ev, 
-                                  void *data )
-{
-   struct player_device_skate *s = dev->storage;
-
-   if( ev == k_player_device_event_bind )
-      player_skate_bind( dev, player );
-   else if( ev == k_player_device_event_respawn )
-      player_skate_reset( dev, player, data );
-   else if( ev == k_player_device_event_pre_update )
-   {
-      if( vg_input_button_down( player->input_use ) )
-      {
-         struct device_transition_walk inf;
-         v3_copy( player->cam.angles, inf.angles );
-         inf.angles[2] = 0.0f;
-
-         player_transition_to_device( player, s->device_id_walk, &inf );
-         return 1;
-      }
-   }
-   else if( ev == k_player_device_event_custom_transition )
-   {
-      /* transition coming in from walking */
-      struct device_transition_skateboard *inf = data;
-
-      q_axis_angle( player->rb.q, (v3f){0.0f,1.0f,0.0f}, 
-                    atan2f( inf->dir[0], inf->dir[2] ) );
-      v3_copy( player->cam.pos, s->state.posl );
-
-      m3x3f temp;
-      euler_m3x3( player->cam.angles, temp );
-      v3_muls( temp[2], -1.0f, s->state.dirl );
-
-      rb_update_transform( &player->rb );
-      v3_muladds( player->rb.co, player->rb.to_world[1], 1.0f, s->state.cog );
-      v3_copy( player->rb.v, s->state.cog_v );
-   }
-   else if( ev == k_player_device_event_update )
-   {
-      player_skate_update( dev, player );
-   }
-   else if( ev == k_player_device_event_post_update )
-   {
-      for( int i=0; i<s->prediction_count; i++ )
-      {
-         struct land_prediction *p = &s->predictions[i];
-         
-         for( int j=0; j<p->log_length - 1; j ++ )
-            vg_line( p->log[j], p->log[j+1], p->colour );
-
-         vg_line_cross( p->log[p->log_length-1], p->colour, 0.25f );
-
-         v3f p1;
-         v3_add( p->log[p->log_length-1], p->n, p1 );
-         vg_line( p->log[p->log_length-1], p1, 0xffffffff );
-
-         vg_line_pt3( p->apex, 0.02f, 0xffffffff );
-      }
-
-      vg_line_pt3( s->state.apex, 0.200f, 0xff0000ff );
-      vg_line_pt3( s->state.apex, 0.201f, 0xff00ffff );
-   }
-   else if( ev == k_player_device_event_animate )
-   {
-      player_skate_animate( dev, player );
-   }
-   else if( ev == k_player_device_event_post_animate )
-   {
-      player_skate_post_animate( dev, player );
-   }
-   else if( ev == k_player_device_event_debug_ui )
-   {
-      player_skate_ui( dev, player );
-   }
-   else
-      return 0;
-
-   return 1;
-}
-
-VG_STATIC player_device player_device_skate =
-{
-   .name          = "skateboard",
-   .event         = player_skate_event,
-   .storage       = &localplayer_device_skate
-};
-
-#endif /* PLAYER_DEVICE_SKATE_H */