a fairly major physics update
[carveJwlIkooP6JGAAIwe30JlM.git] / player_physics.h
index 86f49a0f34a824554f25d409e8cc23879cb824aa..6a744e877a4e8107fb6aa16645e04e5e03353de2 100644 (file)
@@ -59,6 +59,164 @@ grind_edge *player_grind_collect_edge( v3f p0, v3f p1,
    return closest_edge;
 }
 
+/*
+ * Cast a sphere from a to b and see what time it hits
+ */
+VG_STATIC int spherecast_world( v3f pa, v3f pb, float r, float *t, v3f n )
+{
+   struct player_phys *phys = &player.phys;
+
+   bh_iter it;
+   bh_iter_init( 0, &it );
+
+   boxf region;
+   box_init_inf( region );
+   box_addpt( region, pa );
+   box_addpt( region, pb );
+   
+   v3_add( (v3f){ r, r, r}, region[1], region[1] );
+   v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
+
+   v3f dir;
+   v3_sub( pb, pa, dir );
+
+   int hit = -1;
+   float min_t = 1.0f;
+
+   int idx;
+   while( bh_next( world.geo_bh, &it, region, &idx ) )
+   {
+      u32 *ptri = &world.scene_geo->arrindices[ idx*3 ];
+      v3f tri[3];
+
+      boxf box;
+      box_init_inf( box );
+
+      for( int j=0; j<3; j++ )
+      {
+         v3_copy( world.scene_geo->arrvertices[ptri[j]].co, tri[j] );
+         box_addpt( box, tri[j] );
+      }
+
+      v3_add( (v3f){ r, r, r}, box[1], box[1] );
+      v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
+      if( !ray_aabb( box, pa, dir, 1.0f ) )
+         continue;
+      
+      float t;
+      v3f n1;
+      if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) )
+      {
+         if( t < min_t )
+         {
+            min_t = t;
+            hit = idx;
+            v3_copy( n1, n );
+         }
+      }
+   }
+
+   *t = min_t;
+   return hit;
+}
+
+/* 
+ * Trace a path given a velocity rotation.
+ * Closest to 0 is best.
+ */
+VG_STATIC void player_predict_land( m3x3f vr, 
+                                    struct land_prediction *prediction )
+{
+   struct player_phys *phys = &player.phys;
+
+   float pstep  = VG_TIMESTEP_FIXED * 10.0f;
+   float k_bias = 0.96f;
+
+   v3f pco, pco1, pv;
+   v3_copy( phys->rb.co, pco );
+   v3_muls( phys->rb.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;
+
+   prediction->log_length = 0;
+
+   for( int i=0; i<vg_list_size(prediction->log); i++ )
+   {
+      v3_copy( pco, pco1 );
+      apply_gravity( pv, pstep );
+
+      m3x3_mulv( vr, pv, pv );
+      v3_muladds( pco, pv, pstep, pco );
+      
+      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 = player_grind_collect_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 ++ ] ); 
+         break;
+      }
+
+      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;
+   }
+}
+
 /*
  * Called when launching into the air to predict and adjust trajectories
  */
@@ -68,121 +226,79 @@ VG_STATIC void player_start_air(void)
 
    float pstep = VG_TIMESTEP_FIXED * 10.0f;
    float best_velocity_delta = -9999.9f;
-   float k_bias = 0.96f;
 
    v3f axis;
    v3_cross( phys->rb.up, phys->rb.v, axis );
    v3_normalize( axis );
-   player.land_log_count = 0;
+   player.prediction_count = 0;
    
    m3x3_identity( phys->vr );
 
+   float 
+         best_vmod   = 0.0f,
+         min_score   =  INFINITY,
+         max_score   = -INFINITY;
+
+   /*
+    * Search a broad selection of futures
+    */
    for( int m=-3;m<=12; m++ )
    {
-      struct land_log *log = &player.land_log[ player.land_log_count ++ ];
-      log->count = 0;
-      log->colour = 0xff000000;
+      struct land_prediction *p = 
+         &player.predictions[ player.prediction_count ++ ];
 
       float vmod = ((float)m / 15.0f)*0.09f;
 
-      v3f pco, pco1, pv;
-      v3_copy( phys->rb.co, pco );
-      v3_muls( phys->rb.v, k_bias, pv );
-
-      /* 
-       * Try different 'rotations' of the velocity to find the best possible
-       * landing normal. This conserves magnitude at the expense of slightly
-       * unrealistic results
-       */
-
       m3x3f vr;
       v4f vr_q;
 
       q_axis_angle( vr_q, axis, vmod );
       q_m3x3( vr_q, vr );
 
-      m3x3_mulv( vr, pv, pv );
-      v3_muladds( pco, pv, pstep, pco );
-
-      struct grind_edge *best_grind = NULL;
-      float closest_grind = INFINITY;
+      player_predict_land( vr, p );
 
-      for( int i=0; i<50; i++ )
+      if( p->type != k_prediction_none )
       {
-         v3_copy( pco, pco1 );
-         apply_gravity( pv, pstep );
-
-         m3x3_mulv( vr, pv, pv );
-         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 = player_grind_collect_edge( pco, pco1,
-                                                            c0, c1, 0.4f );
-
-         if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
+         if( p->score < min_score )
          {
-            float d2 = v3_dist2( c0, c1 );
-            if( d2 < closest_grind )
-            {
-               closest_grind = d2;
-               best_grind = ge;
-            }
+            min_score = p->score;
+            best_vmod = vmod;
          }
 
-         if( ray_world( pco1, vdir, &contact ))
-         {
-            float land_delta = v3_dot( pv, contact.normal );
-            u32 scolour = (u8)(vg_minf(-land_delta * 2.0f, 255.0f));
+         if( p->score > max_score )
+            max_score = p->score;
+      }
+   }
 
-            /* Bias prediction towords ramps */
-            if( ray_hit_material( &contact )->info.flags 
-                  & k_material_flag_skate_surface )
-            {
-               land_delta *= 0.1f;
-               scolour |= 0x0000a000;
-            }
+   v4f vr_q;
+   q_axis_angle( vr_q, axis, best_vmod*0.1f );
+   q_m3x3( vr_q, phys->vr );
 
-            if( (land_delta < 0.0f) && (land_delta > best_velocity_delta) )
-            {
-               best_velocity_delta = land_delta;
+   q_axis_angle( vr_q, axis, best_vmod );
+   q_m3x3( vr_q, phys->vr_pstep );
 
-               v3_copy( contact.pos, player.land_target );
-               
-               m3x3_copy( vr, phys->vr_pstep );
-               q_axis_angle( vr_q, axis, vmod*0.1f );
-               q_m3x3( vr_q, phys->vr );
-            }
+   /*
+    * Logging
+    */
+   for( int i=0; i<player.prediction_count; i ++ )
+   {
+      struct land_prediction *p = &player.predictions[i];
 
-            v3_copy( contact.pos, log->positions[ log->count ++ ] );
-            log->colour = 0xff000000 | scolour;
-            break;
-         }
+      float l = p->score;
 
-         v3_copy( pco, log->positions[ log->count ++ ] );
+      if( l < 0.0f )
+      {
+         vg_error( "negative score! (%f)\n", l );
       }
 
-      if( best_grind )
-      {
-         log->colour = 0xff0000ff;
-         
-         float score = -closest_grind * 0.05f;
+      l -= min_score;
+      l /= (max_score-min_score);
+      l  = 1.0f - l;
+      l *= 255.0f;
 
-         if( score > best_velocity_delta )
-         {
-            best_velocity_delta = score;
-            
-            m3x3_copy( vr, phys->vr_pstep );
-            q_axis_angle( vr_q, axis, vmod*0.1f );
-            q_m3x3( vr_q, phys->vr );
-         }
-      }
+      p->colour = l;
+      p->colour <<= 8;
+      p->colour |= 0xff000000;
    }
 }
 
@@ -197,6 +313,9 @@ VG_STATIC void player_physics_control_passive(void)
       v2_muladds( phys->grab_mouse_delta, vg.mouse_delta, 0.02f, 
                   phys->grab_mouse_delta );
       v2_normalize_clamp( phys->grab_mouse_delta );
+
+      if( freecam )
+         v2_zero( phys->grab_mouse_delta );
    }
    else
       v2_zero( phys->grab_mouse_delta );
@@ -351,26 +470,33 @@ VG_STATIC void player_physics_control_air(void)
    struct player_phys *phys = &player.phys;
 
    m3x3_mulv( phys->vr, phys->rb.v, phys->rb.v );
-   vg_line_cross( player.land_target, 0xff0000ff, 0.25f );
+   //vg_line_cross( player.land_target, 0xff0000ff, 0.25f );
 
    ray_hit hit;
 
    /* 
     * Prediction 
     */
-   float pstep = VG_TIMESTEP_FIXED * 10.0f;
+   float pstep  = VG_TIMESTEP_FIXED * 1.0f;
+   float k_bias = 0.98f;
 
    v3f pco, pco1, pv;
    v3_copy( phys->rb.co, pco );
-   v3_copy( phys->rb.v, pv );
+   v3_muls( phys->rb.v, 1.0f, pv );
    
    float time_to_impact = 0.0f;
    float limiter = 1.0f;
 
-   for( int i=0; i<50; i++ )
+   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( phys->vr_pstep, pv, pv );
+      m3x3_mulv( phys->vr, pv, pv );
       apply_gravity( pv, pstep );
       v3_muladds( pco, pv, pstep, pco );
       
@@ -380,34 +506,51 @@ VG_STATIC void player_physics_control_air(void)
       v3_sub( pco, pco1, vdir );
       contact.dist = v3_length( vdir );
       v3_divs( vdir, contact.dist, vdir);
+
+      v3f c0, c1;
+      struct grind_edge *ge = player_grind_collect_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 ))
+      if( ray_world( pco1, vdir, &contact ) )
       {
-         float angle = v3_dot( phys->rb.up, contact.normal );
-         v3f axis; 
-         v3_cross( phys->rb.up, contact.normal, axis );
-
+         v3_copy( contact.normal, target_normal );
+         has_target = 1;
          time_to_impact += (contact.dist/orig_dist)*pstep;
-         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)*3.0f*VG_TIMESTEP_FIXED );
-            q_mul( correction, phys->rb.q, phys->rb.q );
-         }
-
          vg_line_cross( contact.pos, 0xffff0000, 0.25f );
          break;
       }
       time_to_impact += pstep;
    }
 
+   if( has_target )
+   {
+      float angle = v3_dot( phys->rb.up, target_normal );
+      v3f axis; 
+      v3_cross( phys->rb.up, 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)*3.0f*VG_TIMESTEP_FIXED );
+         q_mul( correction, phys->rb.q, phys->rb.q );
+      }
+   }
+
    v2f steer = { player.input_js1h->axis.value,
                  player.input_js1v->axis.value };
 
@@ -455,6 +598,40 @@ VG_STATIC void player_walk_update_collision(void)
 }
 
 VG_STATIC void player_integrate(void);
+
+VG_STATIC int player_walk_surface_standable( v3f n )
+{
+   return v3_dot( n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f;
+}
+
+VG_STATIC void player_walk_stepdown(void)
+{
+   struct player_phys *phys = &player.phys;
+   float max_dist = 0.4f;
+
+   v3f pa, pb;
+   v3_copy( phys->rb.co, pa );
+   pa[1] += 0.3f;
+
+   v3_muladds( pa, (v3f){0.01f,1.0f,0.01f}, -max_dist, pb );
+   vg_line( pa, pb, 0xff000000 );
+
+   /* TODO: Make #define */
+   float r = 0.3f,
+         t;
+
+   v3f n;
+   if( spherecast_world( pa, pb, r, &t, n ) != -1 )
+   {
+      if( player_walk_surface_standable( n ) )
+      {
+         phys->in_air = 0;
+         v3_lerp( pa, pb, t+0.001f, phys->rb.co );
+         phys->rb.co[1] -= 0.3f;
+      }
+   }
+}
+
 /*
  * Entire Walking physics model
  * TODO: sleep when under certain velotiy
@@ -479,6 +656,9 @@ VG_STATIC void player_walk_physics(void)
 
    v2f walk = { player.input_walkh->axis.value,
                 player.input_walkv->axis.value };
+
+   if( freecam )
+      v2_zero( walk );
    
    if( v2_length2(walk) > 0.001f )
       v2_normalize_clamp( walk );
@@ -607,12 +787,31 @@ VG_STATIC void player_walk_physics(void)
          phys->in_air = 1;
          return;
       }
+
+      /* Check if grounded by current manifold */
+      phys->in_air = 1;
+      for( int i=0; i<len; i++ )
+      {
+         struct contact *ct = &manifold[i];
+         if( player_walk_surface_standable( ct->n ) )
+            phys->in_air = 0;
+      }
       
+      /* otherwise... */
+      if( phys->in_air )
+         player_walk_stepdown();
+      
+#if 0
       /* if we've put us in the air, step down slowly */
       phys->in_air = 1;
       float max_dist = 0.3f,
             start_y = phys->rb.co[1];
 
+      v3f pa, pb;
+      v3_copy( phys->rb.co, pa );
+      v3_muladds( pa, (v3f){0.0f,1.0f,0.0f}, -max_dist, pb );
+
+
       for( int j=0; j<8; j++ )
       {
          for( int i=0; i<len; i++ )
@@ -652,6 +851,7 @@ VG_STATIC void player_walk_physics(void)
       
       /* Transitioning into air mode */
       phys->rb.co[1] = start_y;
+#endif
    }
 }
 
@@ -687,8 +887,8 @@ VG_STATIC int player_update_grind_collision( rb_ct *contact )
    v3f p0, p1, c0, c1;
    v3_muladds( phys->rb.co, phys->rb.forward,  0.5f, p0 );
    v3_muladds( phys->rb.co, phys->rb.forward, -0.5f, p1 );
-   v3_muladds( p0, phys->rb.up, 0.125f, p0 );
-   v3_muladds( p1, phys->rb.up, 0.125f, p1 );
+   v3_muladds( p0, phys->rb.up, 0.125f-0.15f, p0 );
+   v3_muladds( p1, phys->rb.up, 0.125f-0.15f, p1 );
 
    float const k_r = 0.25f;
    struct grind_edge *closest_edge = player_grind_collect_edge( p0, p1, 
@@ -706,12 +906,9 @@ VG_STATIC int player_update_grind_collision( rb_ct *contact )
       v3f delta;
       v3_sub( c1, c0, delta );
 
-      if( v3_dot( delta, phys->rb.up ) > 0.0f )
+      if( v3_dot( delta, phys->rb.up ) > 0.0001f )
       {
-         v3_copy( delta, contact->n );
-         float l = v3_length( contact->n );
-         v3_muls( contact->n, 1.0f/l, contact->n );
-         contact->p = l;
+         contact->p = v3_length( delta );
          contact->type = k_contact_type_edge;
          contact->element_id = 0;
          v3_copy( c1, contact->co );
@@ -724,6 +921,10 @@ VG_STATIC int player_update_grind_collision( rb_ct *contact )
          v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
          v3_cross( edge_dir, axis_dir, contact->n );
 
+#if 0
+         vg_info( "%f %f\n", v3_length( contact->n ), contact->p );
+#endif
+
          return 1;
       }
       else
@@ -738,6 +939,9 @@ VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
 {
    struct player_phys *phys = &player.phys;
 
+   phys->rise = vg_lerpf( phys->rise, phys->in_air? -0.25f: 0.0f, 
+                          VG_TIMESTEP_FIXED );
+
    rigidbody *rbf = &player.collide_front,
              *rbb = &player.collide_back;
 
@@ -745,7 +949,7 @@ VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
    m3x3_copy( phys->rb.to_world, player.collide_back.to_world );
    
    player.air_blend = vg_lerpf( player.air_blend, phys->in_air, 0.1f );
-   float h = player.air_blend*0.2f;
+   float h = player.air_blend*0.0f;
 
    m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h,-k_board_length}, rbf->co );
    v3_copy( rbf->co, rbf->to_world[3] );
@@ -772,7 +976,11 @@ VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
       rb_manifold_filter_joint_edges( manifold, len_f, 0.05f );
       rb_manifold_filter_pairs( manifold, len_f, 0.05f );
    }
-   len_f = rb_manifold_apply_filtered( manifold, len_f );
+   int new_len_f = rb_manifold_apply_filtered( manifold, len_f );
+   if( len_f && !new_len_f )
+      len_f = 1;
+   else
+      len_f = new_len_f;
    
    rb_ct *man_b = &manifold[len_f];
    len_b = rb_sphere_scene( rbb, &world.rb_geo, man_b );
@@ -783,8 +991,11 @@ VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
       rb_manifold_filter_joint_edges( man_b, len_b, 0.05f );
       rb_manifold_filter_pairs( man_b, len_b, 0.05f );
    }
-   len_b = rb_manifold_apply_filtered( man_b, len_b );
-
+   int new_len_b = rb_manifold_apply_filtered( man_b, len_b );
+   if( len_b && !new_len_b )
+      len_b = 1;
+   else
+      len_b = new_len_b;
 #if 0
    /* 
     * Preprocess collision points, and create a surface picture.
@@ -918,8 +1129,8 @@ VG_STATIC void player_collision_response( rb_ct *manifold, int len )
           * components.
           */
          
-         float wy = v3_dot( phys->rb.up, impulse ),
-               wx = v3_dot( phys->rb.right, impulse )*1.8f;
+         float wy = v3_dot( phys->rb.up, impulse ) * 0.8f,
+               wx = v3_dot( phys->rb.right, impulse )*1.0f;
 
          v3_muladds( phys->rb.w, phys->rb.up, wy, phys->rb.w );
          v3_muladds( phys->rb.w, phys->rb.right, wx, phys->rb.w );
@@ -980,11 +1191,12 @@ VG_STATIC void player_do_motion(void)
          phys->grind = 1;
          v3f up = { 0.0f, 1.0f, 0.0f };
          float angle = v3_dot( phys->rb.up, up );
-         v3f axis; 
-         v3_cross( phys->rb.up, up, axis );
 
          if( fabsf(angle) < 0.99f )
          {
+            v3f axis; 
+            v3_cross( phys->rb.up, up, axis );
+
             v4f correction;
             q_axis_angle( correction, axis, 
                   VG_TIMESTEP_FIXED * 10.0f * acosf(angle) );
@@ -1051,7 +1263,7 @@ VG_STATIC void player_do_motion(void)
    
    
    /* Real angular velocity integration */
-   v3_lerp( phys->rb.w, (v3f){0.0f,0.0f,0.0f}, 0.125f, phys->rb.w );
+   v3_lerp( phys->rb.w, (v3f){0.0f,0.0f,0.0f}, 0.125f*0.5f, phys->rb.w );
    if( v3_length2( phys->rb.w ) > 0.0f )
    {
       v4f rotation;
@@ -1091,6 +1303,15 @@ VG_STATIC void player_do_motion(void)
          m3x3_mulv( gate->transport, phys->m, phys->m );
          m3x3_mulv( gate->transport, phys->bob, phys->bob );
 
+         /* Pre-emptively edit the camera matrices so that the motion vectors 
+          * are correct */
+         m4x3f transport_i;
+         m4x4f transport_4;
+         m4x3_invert_affine( gate->transport, transport_i );
+         m4x3_expand( transport_i, transport_4 );
+         m4x4_mul( main_camera.mtx.pv, transport_4, main_camera.mtx.pv );
+         m4x4_mul( main_camera.mtx.v, transport_4, main_camera.mtx.v );
+
          v4f transport_rotation;
          m3x3_q( gate->transport, transport_rotation );
          q_mul( transport_rotation, phys->rb.q, phys->rb.q );
@@ -1126,31 +1347,31 @@ VG_STATIC void player_freecam(void)
 {
    player_mouseview();
 
-   float movespeed = fc_speed;
+   float movespeed = fc_speed * VG_TIMESTEP_FIXED;
    v3f lookdir = { 0.0f, 0.0f, -1.0f },
        sidedir = { 1.0f, 0.0f,  0.0f };
    
-   m3x3_mulv( camera_mtx, lookdir, lookdir );
-   m3x3_mulv( camera_mtx, sidedir, sidedir );
+   m3x3_mulv( main_camera.transform, lookdir, lookdir );
+   m3x3_mulv( main_camera.transform, sidedir, sidedir );
    
    static v3f move_vel = { 0.0f, 0.0f, 0.0f };
 
-   /* TODO */
-#if 0
-   if( vg_get_button( "forward" ) )
-      v3_muladds( move_vel, lookdir, VG_TIMESTEP_FIXED * movespeed, move_vel );
-   if( vg_get_button( "back" ) )
-      v3_muladds( move_vel, lookdir, VG_TIMESTEP_FIXED *-movespeed, move_vel );
-   if( vg_get_button( "left" ) )
-      v3_muladds( move_vel, sidedir, VG_TIMESTEP_FIXED *-movespeed, move_vel );
-   if( vg_get_button( "right" ) )
-      v3_muladds( move_vel, sidedir, VG_TIMESTEP_FIXED * movespeed, move_vel );
-#endif
+   v2f steer = { player.input_js1h->axis.value,
+                 player.input_js1v->axis.value };
+
+   v3_muladds( move_vel, sidedir,  movespeed*steer[0], move_vel );
+   v3_muladds( move_vel, lookdir, -movespeed*steer[1], move_vel );
 
    v3_muls( move_vel, 0.7f, move_vel );
    v3_add( move_vel, player.camera_pos, player.camera_pos );
 }
 
+VG_STATIC int kill_player( int argc, char const *argv[] )
+{
+   player_kill();
+   return 0;
+}
+
 VG_STATIC int reset_player( int argc, char const *argv[] )
 {
    struct player_phys *phys = &player.phys;
@@ -1193,6 +1414,13 @@ VG_STATIC int reset_player( int argc, char const *argv[] )
    if( !rp )
    {
       vg_error( "No spawn found\n" );
+      vg_info( "Player position: %f %f %f\n", player.phys.rb.co[0],
+                                              player.phys.rb.co[1],
+                                              player.phys.rb.co[2] );
+      vg_info( "Player velocity: %f %f %f\n", player.phys.rb.v[0],
+                                              player.phys.rb.v[1],
+                                              player.phys.rb.v[2] );
+
       if( !world.spawn_count )
          return 0;
 
@@ -1206,10 +1434,12 @@ VG_STATIC int reset_player( int argc, char const *argv[] )
 
    v3f delta = {1.0f,0.0f,0.0f};
    m3x3_mulv( the_long_way, delta, delta );
-
-   player.angles[0] = atan2f( delta[0], -delta[2] ); 
-   player.angles[1] = -asinf( delta[1] );
-
+   
+   if( !freecam )
+   {
+      player.angles[0] = atan2f( delta[0], -delta[2] ); 
+      player.angles[1] = -asinf( delta[1] );
+   }
 
    v4_copy( rp->q, phys->rb.q );
    v3_copy( rp->co, phys->rb.co );