LPR - Walking
authorhgn <hgodden00@gmail.com>
Wed, 25 Jan 2023 09:02:38 +0000 (09:02 +0000)
committerhgn <hgodden00@gmail.com>
Wed, 25 Jan 2023 09:02:38 +0000 (09:02 +0000)
18 files changed:
camera.h
common.h
maps_src/mp_gridmap.mdl
menu.h
player.h
player_animation.h
player_audio.h
player_device_walk.h [new file with mode: 0644]
player_interface.h [new file with mode: 0644]
player_model.h
player_physics.h
player_physics_skate.h [new file with mode: 0644]
player_physics_walk.h [new file with mode: 0644]
rigidbody.h
skaterift.c
vehicle.h
world.h
world_gen.h

index 596409937ceb3cf2f532c7d22f2c999533bd9fa7..e818e8fed096e14bfdb8f4ca67495df3f10bec8b 100644 (file)
--- a/camera.h
+++ b/camera.h
@@ -8,7 +8,7 @@ typedef struct camera camera;
 struct camera
 {
    /* Input */
-   v2f angles;
+   v3f angles;
    v3f pos;
    float fov, nearz, farz;
 
index bb70596c8c30334587a45f47ee9e4c19dd0e4b58..14bf294e9f9b430f9a6ac292e688acec1d8a8899 100644 (file)
--- a/common.h
+++ b/common.h
@@ -96,4 +96,39 @@ VG_STATIC void str_utf8_collapse( const char *str, char *buf, u32 length )
    buf[j] = 0x00;
 }
 
+VG_STATIC float 
+   k_runspeed              = 20.0f,    /* depr */
+   k_board_radius          = 0.3f,
+   k_board_length          = 0.45f,
+   k_board_allowance       = 0.04f,
+   k_friction_lat          = 12.0f,
+   k_friction_resistance   = 0.01f,
+   k_max_push_speed        = 16.0f,
+   k_push_accel            = 10.0f,
+   k_push_cycle_rate       = 8.0f,
+   k_steer_ground          = 2.5f,
+   k_steer_air             = 3.6f,
+   k_steer_air_lerp        = 0.3f,
+   k_pump_force            = 0.0f,
+   k_downforce             = 5.0f,
+   k_walk_downforce        = 8.0f,
+   k_jump_charge_speed     = (1.0f/1.0f),
+   k_jump_force            = 5.0f,
+   k_pitch_limit           = 1.5f,
+   k_look_speed            = 2.0f,
+
+   k_cog_spring            = 0.2f,
+   k_cog_damp              = 0.02f,
+   k_cog_mass_ratio        = 0.9f,
+
+   k_mmthrow_scale         = 6.0f,
+   k_mmcollect_lat         = 2.0f,
+   k_mmcollect_vert        = 0.0f,
+   k_mmdecay               = 12.0f,
+   k_spring_angular        = 1.0f,
+
+   k_spring_force          = 15.0f,
+   k_spring_dampener       = 5.0f;
+
+
 #endif /* COMMON_H */
index d9e1fb79c427633325eef402929a10bc92f963d2..6bcb5c9581d17d4589b2bc514700f70296984ab8 100644 (file)
Binary files a/maps_src/mp_gridmap.mdl and b/maps_src/mp_gridmap.mdl differ
diff --git a/menu.h b/menu.h
index d8b686452f73015bb1d47e7844ed2eb55c5e38ab..4f6564407990d6346ac91320bd051036dd836309 100644 (file)
--- a/menu.h
+++ b/menu.h
@@ -725,6 +725,7 @@ VG_STATIC void menu_update(void)
    }
    
    /* Update camera */
+#if 0
    {
       main_camera.angles[0] = 
          vg_alerpf( main_camera.angles[0], angles[0], menu_opacity );
@@ -734,6 +735,7 @@ VG_STATIC void menu_update(void)
 
       camera_update_transform( &main_camera );
    }
+#endif
 
    float dt = vg.frame_delta * 6.0f;
    menu_opacity = vg_lerpf( menu_opacity, cl_menu&&!cl_menu_go_away, dt );
index 119b3f71348fde480427b721ec77860be2d0b6d2..e625b0baf735c95de4180c7af173a754afd77599 100644 (file)
--- a/player.h
+++ b/player.h
@@ -2,6 +2,7 @@
  * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
 
+#define PLAYER_H
 #ifndef PLAYER_H
 #define PLAYER_H
 
 #include "skeleton.h"
 #include "bvh.h"
 
-VG_STATIC float 
-   k_walkspeed             = 12.0f,
-   k_air_accelerate        = 20.0f,
-   k_runspeed              = 20.0f,
-   k_board_radius          = 0.3f,
-   k_board_length          = 0.45f,
-   k_board_allowance       = 0.04f,
-   k_friction_lat          = 12.0f,
-   k_friction_resistance   = 0.01f,
-   k_max_push_speed        = 16.0f,
-   k_push_accel            = 10.0f,
-   k_push_cycle_rate       = 8.0f,
-   k_steer_ground          = 2.5f,
-   k_steer_air             = 3.6f,
-   k_steer_air_lerp        = 0.3f,
-   k_pump_force            = 0.0f,
-   k_downforce             = 5.0f,
-   k_walk_downforce        = 8.0f,
-   k_jump_charge_speed     = (1.0f/1.0f),
-   k_jump_force            = 5.0f,
-   k_pitch_limit           = 1.5f,
-   k_look_speed            = 2.0f,
-   k_walk_accel            = 150.0f,
-   k_walk_friction         = 8.0f,
-
-   k_cog_spring            = 0.2f,
-   k_cog_damp              = 0.02f,
-   k_cog_mass_ratio        = 0.9f,
-
-   k_mmthrow_scale         = 6.0f,
-   k_mmcollect_lat         = 2.0f,
-   k_mmcollect_vert        = 0.0f,
-   k_mmdecay               = 12.0f,
-   k_spring_angular        = 1.0f,
-
-   k_spring_force          = 15.0f,
-   k_spring_dampener       = 5.0f;
 
 VG_STATIC int freecam = 0;
 VG_STATIC int walk_grid_iterations = 1;
@@ -65,38 +29,20 @@ VG_STATIC int cl_thirdperson = 0;
 
 VG_STATIC struct gplayer
 {
-   /* Physics */
-   rigidbody collide_front, collide_back;
+   rigidbody rb, rb_frame;
+   v3f co, angles;         /* used as transfer between controllers */
 
-   struct player_phys
+   enum player_controller
    {
-      rigidbody rb, rb_gate_frame;
-      float iY, siY;   /* Yaw inertia */
-
-      v3f a, v_last, m, bob, vl;
-
-      /* Utility */
-      float vswitch, slip, slip_last, reverse;
-      float grab, jump, pushing, push_time, rise;
-      v2f grab_mouse_delta;
-
-      v3f throw_v;
-      v3f cog, 
-          cog_v;
-
-      int lift_frames;
-
-      double start_push;
-      int in_air, on_board, jump_charge, jump_dir, grind;
-
-      m3x3f vr,vr_pstep;
+      k_player_controller_walk,
+      k_player_controller_skate,
+      k_player_controller_ragdoll,
+      k_player_controller_mountain_skate,
+      k_player_controller_snowboard,
+      k_player_controller_drive
    }
-   phys, 
-   phys_gate_frame;
-
-   float normal_pressure;
-   v3f debug_mmcollect_lat,
-       debug_mmcollect_vert;
+   controller,
+   controller_frame;
 
    m4x3f visual_transform,
          inv_visual_transform;
@@ -104,25 +50,6 @@ VG_STATIC struct gplayer
    int is_dead, death_tick_allowance, rewinding;
    int rewind_sound_wait;
 
-   struct land_prediction
-   {
-      v3f   log[50];
-      v3f   n;
-      u32   log_length;
-      float score;
-
-      enum prediction_type
-      {
-         k_prediction_none,
-         k_prediction_land,
-         k_prediction_grind
-      }
-      type;
-
-      u32   colour;
-   }
-   predictions[22];
-   u32 prediction_count;
 
    v3f handl_target, handr_target,
        handl, handr;
@@ -142,16 +69,13 @@ VG_STATIC struct gplayer
                         *input_grab;
    
    /* Camera */
-   float air_blend;
    float air_time;
-   
    v3f camera_pos, smooth_localcam;
-   v2f angles;
 
    struct rewind_frame
    {
       v3f pos;
-      v2f ang;
+      v3f ang;
    }
    *rewind_buffer;
    u32 rewind_incrementer,
@@ -241,11 +165,17 @@ VG_STATIC struct gplayer
    }
    mdl;
 }
-player = 
+player__OLD
+
+#if 0
+= 
 {
    .collide_front = { .type = k_rb_shape_sphere, .inf.sphere.radius = 0.3f },
    .collide_back  = { .type = k_rb_shape_sphere, .inf.sphere.radius = 0.3f }
-};
+}
+#endif
+
+;
 
 /* 
  * API 
@@ -263,11 +193,20 @@ VG_STATIC void player_save_rewind_frame(void);
 VG_STATIC void player_mouseview(void);
 
 #include "player_physics.h"
+#include "player_physics_skate.h"
+#include "player_physics_walk.h"
 #include "player_ragdoll.h"
 #include "player_model.h"
 #include "player_animation.h"
 #include "player_audio.h"
 
+/*
+ * player_physics_<INTERFACE>_<SUB-INTERFACE>
+ *
+ *
+ *
+ */
+
 /* 
  * -----------------------------------------------------------------------------
  *                                    Events
@@ -279,6 +218,7 @@ VG_STATIC void reset_player_poll( int argc, char const *argv[] );
 
 VG_STATIC void player_init(void)                                         /* 1 */
 {
+#if 0
    player.input_js1h = vg_create_named_input( "steer-h", k_input_type_axis );
    player.input_js1v = vg_create_named_input( "steer-v", k_input_type_axis );
    player.input_grab = vg_create_named_input( "grab", k_input_type_axis_norm );
@@ -338,13 +278,15 @@ VG_STATIC void player_init(void)                                         /* 1 */
 
    for( int i=0; i<vg_list_size(default_cfg); i++ )
       vg_execute_console_input(default_cfg[i]);
+#endif
 
-   rb_init( &player.phys.rb );
-   rb_init( &player.collide_front );
-   rb_init( &player.collide_back  );
+   rb_init( &player.rb );
 
    VG_VAR_F32( k_walkspeed );
-   VG_VAR_F32( k_air_accelerate );
+   VG_VAR_F32( k_stopspeed );
+   VG_VAR_F32( k_airspeed );
+   VG_VAR_F32( k_walk_friction );
+   VG_VAR_F32( k_walk_air_accel );
    VG_VAR_F32( k_runspeed );
    VG_VAR_F32( k_walk_accel );
 
@@ -423,6 +365,7 @@ VG_STATIC void player_save_rewind_frame(void)
 VG_STATIC int         menu_enabled(void);
 #include "menu.h"
 
+VG_STATIC void player_do_motion(void);
 /*
  * Free camera movement
  */
@@ -469,8 +412,6 @@ VG_STATIC void player_mouseview(void)
 /* Deal with input etc */
 VG_STATIC void player_update_pre(void)
 {
-   struct player_phys *phys = &player.phys;
-
 
    {
       v3f ra, rb, rx;
@@ -492,6 +433,7 @@ VG_STATIC void player_update_pre(void)
       }
    }
 
+#if 0
 
    vg_line_pt3( phys->cog, 0.10f, 0xffffffff );
    vg_line_pt3( phys->cog, 0.09f, 0xffffffff );
@@ -507,6 +449,7 @@ VG_STATIC void player_update_pre(void)
    vg_line( spring_end, throw_end, VG__RED );
    vg_line( spring_end, p0, VG__GREEN );
    vg_line( spring_end, p1, VG__BLUE );
+#endif
 
    if( player.rewinding )
       return;
@@ -542,10 +485,10 @@ VG_STATIC void player_update_pre(void)
          player.death_tick_allowance = 30;
          player_restore_frame();
 
-         if( !phys->on_board )
+         if( player.controller == k_player_controller_walk )
          {
-            player.angles[0] = atan2f( -phys->rb.forward[2], 
-                                       -phys->rb.forward[0] );
+            player.angles[0] = atan2f( -player.rb.forward[2], 
+                                       -player.rb.forward[0] );
          }
 
          player.mdl.shoes[0] = 1;
@@ -574,23 +517,28 @@ VG_STATIC void player_update_pre(void)
 
    if( vg_input_button_down( player.input_switch_mode ) && !menu_enabled() )
    {
-      phys->on_board ^= 0x1;
-
       audio_lock();
-      if( phys->on_board )
+
+#if 0
+      if( phys->controller == k_player_controller_walk )
       {
+         phys->controller = k_player_controller_skate;
+
          v3_muladds( phys->rb.v, phys->rb.forward, 0.2f, phys->rb.v );
          audio_play_oneshot( &audio_lands[6], 1.0f );
       }
-      else
+      else if( phys->controller == k_player_controller_skate )
       {
+         phys->controller = k_player_controller_walk;
+
          audio_play_oneshot( &audio_lands[5], 1.0f );
       }
+#endif
 
       audio_unlock();
    }
 
-   if( !phys->on_board )
+   if( player.controller == k_player_controller_walk )
       player_mouseview();
 }
 
@@ -602,8 +550,6 @@ VG_STATIC void player_update_fixed(void)                                 /* 2 */
    if( player.death_tick_allowance )
       player.death_tick_allowance --;
 
-   struct player_phys *phys = &player.phys;
-
    if( player.is_dead )
    {
       player_ragdoll_iter();
@@ -623,6 +569,7 @@ VG_STATIC void player_update_fixed(void)                                 /* 2 */
 
 VG_STATIC void player_update_post(void)
 {
+#if 0
    for( int i=0; i<player.prediction_count; i++ )
    {
       struct land_prediction *p = &player.predictions[i];
@@ -636,7 +583,9 @@ VG_STATIC void player_update_post(void)
       v3_add( p->log[p->log_length-1], p->n, p1 );
       vg_line( p->log[p->log_length-1], p1, 0xffffffff );
    }
+#endif
 
+#if 0
    if( player.is_dead )
    {
       player_debug_ragdoll();
@@ -658,12 +607,14 @@ VG_STATIC void player_update_post(void)
    }
 
    if( freecam )
+#endif
       player_freecam();
 
    /* CAMERA POSITIONING: LAYER 0 */
    v2_copy( player.angles, main_camera.angles );
    v3_copy( player.camera_pos, main_camera.pos );
 
+#if 0
    if( player.rewinding )
    {
       if( player.rewind_time <= 0.0f )
@@ -778,6 +729,7 @@ VG_STATIC void player_update_post(void)
          v3_lerp( override_pos, player.camera_pos, c, main_camera.pos );
       }
    }
+#endif
 
    camera_update_transform( &main_camera );
    player_audio();
@@ -803,6 +755,123 @@ VG_STATIC void draw_player( camera *cam )
    mesh_draw( &player.mdl.player_meshes[cl_playermdl_id] );
 }
 
+VG_STATIC void player_do_motion(void)
+{
+   if( world.water.enabled )
+   {
+      if( (player.rb.co[1] < 0.0f) && !player.is_dead )
+      {
+         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_splash );
+         audio_unlock();
+
+         player_kill();
+      }
+   }
+
+   v3f prevco;
+   v3_copy( player.rb.co, prevco );
+
+   if( player.controller == k_player_controller_skate )
+   {
+#if 0
+      player_skate_update();
+#endif
+   }
+   else
+      player_walk_physics( &player_walky );
+   
+   
+   /* Real angular velocity integration */
+#if 0
+   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;
+      v3f axis;
+      v3_copy( phys->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, phys->rb.q, phys->rb.q );
+   }
+
+   /* Faux angular velocity */
+   v4f rotate; 
+
+   float lerpq = (player_skate.activity == k_skate_activity_air)? 0.04f: 0.3f;
+   phys->siY = vg_lerpf( phys->siY, phys->iY, lerpq );
+
+   q_axis_angle( rotate, phys->rb.up, phys->siY );
+   q_mul( rotate, phys->rb.q, phys->rb.q );
+   phys->iY = 0.0f;
+#endif
+
+   /* 
+    * Gate intersection, by tracing a line over the gate planes 
+    */
+#if 0
+   for( int i=0; i<world.gate_count; i++ )
+   {
+      struct route_gate *rg = &world.gates[i];
+      teleport_gate *gate = &rg->gate;
+
+      if( gate_intersect( gate, phys->rb.co, prevco ) )
+      {
+         m4x3_mulv( gate->transport, phys->rb.co, phys->rb.co );
+         m4x3_mulv( gate->transport, phys->cog, phys->cog );
+         m3x3_mulv( gate->transport, phys->cog_v, phys->cog_v );
+         m3x3_mulv( gate->transport, phys->rb.v, phys->rb.v );
+         m3x3_mulv( gate->transport, phys->vl, phys->vl );
+         m3x3_mulv( gate->transport, phys->v_last, phys->v_last );
+         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 );
+         
+         world_routes_activate_gate( i );
+
+         if( phys->controller == k_player_controller_walk )
+         {
+            v3f fwd_dir = {cosf(player.angles[0]),
+                           0.0f,
+                           sinf(player.angles[0])};
+            m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
+
+            player.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
+         }
+         
+         player.rewind_length = 0;
+         player.rewind_total_length = 0.0f;
+         player.rewind_incrementer = 10000;
+         player_save_frame();
+
+         audio_lock();
+         audio_play_oneshot( &audio_gate_pass, 1.0f );
+         audio_unlock();
+         break;
+      }
+   }
+#endif
+   
+   rb_update_transform( &player.rb );
+}
+
 /* 
  * -----------------------------------------------------------------------------
  *                              API implementation
@@ -811,7 +880,7 @@ VG_STATIC void draw_player( camera *cam )
 
 VG_STATIC float *player_get_pos(void)
 {
-   return player.phys.rb.co;
+   return player.rb.co;
 }
 
 VG_STATIC void player_kill(void)
@@ -819,7 +888,7 @@ VG_STATIC void player_kill(void)
    if( player.death_tick_allowance == 0 )
    {
       player.is_dead = 1;
-      player_ragdoll_copy_model( player.phys.rb.v );
+      player_ragdoll_copy_model( player.rb.v );
    }
 }
 
@@ -829,4 +898,18 @@ VG_STATIC float *player_cam_pos(void)
 }
 
 
+VG_STATIC void player_save_frame(void)
+{
+   player.controller_frame = player.controller;
+   
+   /* TODO         <interface>->save() */
+}
+
+VG_STATIC void player_restore_frame(void)
+{
+   player.controller = player.controller_frame;
+
+   /* TODO         <interface>->load() */
+}
+
 #endif /* PLAYER_H */
index b255f14c71b176d52e3032fc549f6d4891e2f6cc..0660acf10105dea31c16e5b52e851e71b9215e6d 100644 (file)
@@ -6,85 +6,16 @@
 #define PLAYER_ANIMATION_H
 
 #include "player.h"
+#include "player_physics_walk.h"
 
 VG_STATIC void player_animate_offboard(void)
 {
-   {
-      float fly = player.phys.in_air,
-            rate;
-
-      if( player.phys.in_air )
-         rate = 2.4f;
-      else
-         rate = 9.0f;
-
-      player.ffly  = vg_lerpf( player.ffly, fly, rate*vg.time_delta );
-      player.frun  = vg_lerpf( player.frun,
-                               player.walk *
-                               (1.0f + player.input_walk->button.value*0.5f),
-                               2.0f*vg.time_delta );
-   }
-
-   struct player_phys *phys = &player.phys;
-
-   mdl_keyframe apose[32], bpose[32];
-   struct skeleton *sk = &player.mdl.sk;
-
-   if( player.walk > 0.025f )
-   {
-      /* TODO move */
-      float walk_norm = 30.0f/(float)player.mdl.anim_walk->length,
-            run_norm  = 30.0f/(float)player.mdl.anim_run->length,
-            walk_adv  = vg_lerpf( walk_norm, run_norm, player.walk );
-
-      player.walk_timer += walk_adv * vg.time_delta;
-   }
-   else
-   {
-      player.walk_timer = 0.0f;
-   }
-
-   float walk_norm = (float)player.mdl.anim_walk->length/30.0f,
-         run_norm  = (float)player.mdl.anim_run->length/30.0f,
-         t = player.walk_timer,
-         l = vg_clampf( player.frun*15.0f, 0.0f, 1.0f ),
-         idle_walk = vg_clampf( (player.frun-0.1f)/(1.0f-0.1f), 0.0f, 1.0f );
-
-   /* walk/run */
-   skeleton_sample_anim( sk, player.mdl.anim_walk, t*walk_norm, apose );
-   skeleton_sample_anim( sk, player.mdl.anim_run,  t*run_norm, bpose );
-
-   skeleton_lerp_pose( sk, apose, bpose, l, apose );
-
-   /* idle */
-   skeleton_sample_anim( sk, player.mdl.anim_idle, vg.time*0.1f, bpose );
-   skeleton_lerp_pose( sk, apose, bpose, 1.0f-idle_walk, apose );
-
-   /* air */
-   skeleton_sample_anim( sk, player.mdl.anim_jump, vg.time*0.6f, bpose );
-   skeleton_lerp_pose( sk, apose, bpose, player.ffly, apose );
-
-   skeleton_apply_pose( &player.mdl.sk, apose, k_anim_apply_defer_ik );
-   skeleton_apply_ik_pass( &player.mdl.sk );
-   skeleton_apply_pose( &player.mdl.sk, apose, k_anim_apply_deffered_only );
-
-   v3_copy( player.mdl.sk.final_mtx[player.mdl.id_head-1][3], 
-               player.mdl.cam_pos );
-
-   skeleton_apply_inverses( &player.mdl.sk );
-
-   m4x3f mtx;
-   v4f rot;
-   q_axis_angle( rot, (v3f){0.0f,1.0f,0.0f}, -player.angles[0] - VG_PIf*0.5f );
-   q_m3x3( rot, mtx );
-   v3_copy( player.visual_transform[3], mtx[3] );
-
-   skeleton_apply_transform( &player.mdl.sk, mtx );
-   skeleton_debug( &player.mdl.sk );
+   player_walk_animate( &player_walky, &player.mdl.sk, NULL );
 }
 
 VG_STATIC void player_animate(void)
 {
+#if 0
    struct player_phys *phys = &player.phys;
    rb_extrapolate_transform( &player.phys.rb, player.visual_transform );
    v3_muladds( player.visual_transform[3], phys->rb.up, -0.2f, 
@@ -99,7 +30,7 @@ VG_STATIC void player_animate(void)
 
    m4x3_invert_affine( player.visual_transform, player.inv_visual_transform );
 
-   if( !phys->on_board )
+   if( phys->controller == k_player_controller_walk )
    {
       player_animate_offboard();
       return;
@@ -157,9 +88,12 @@ VG_STATIC void player_animate(void)
    
    /* movement information */
    {
+      int iair = (player_skate.activity == k_skate_activity_air) ||
+                 (player_skate.activity == k_skate_activity_grind );
+
       float dirz = phys->reverse > 0.0f? 0.0f: 1.0f,
             dirx = phys->slip < 0.0f?    0.0f: 1.0f,
-            fly  = (phys->in_air|phys->grind)?  1.0f: 0.0f;
+            fly  = iair?                 1.0f: 0.0f;
 
       player.fdirz = vg_lerpf( player.fdirz, dirz, 2.4f*vg.time_delta );
       player.fdirx = vg_lerpf( player.fdirx, dirx, 0.6f*vg.time_delta );
@@ -194,11 +128,13 @@ VG_STATIC void player_animate(void)
       skeleton_lerp_pose( sk, apose, bpose, player.fslide, apose );
 
       /* pushing */
+      double push_time = vg.time - player.phys.start_push;
+
       player.fpush = vg_lerpf( player.fpush, 
-                               player.phys.pushing, 
+                               (vg.time - player.phys.cur_push) < 0.125,
                                6.0f*vg.time_delta );
 
-      float pt = player.phys.push_time + vg.accumulator;
+      float pt = push_time + vg.accumulator;
       if( phys->reverse > 0.0f )
          skeleton_sample_anim( sk, player.mdl.anim_push, pt, bpose );
       else
@@ -306,9 +242,10 @@ VG_STATIC void player_animate(void)
    v3_copy( player.mdl.sk.final_mtx[player.mdl.id_head-1][3], 
                player.mdl.cam_pos );
    skeleton_apply_inverses( &player.mdl.sk );
-   skeleton_apply_transform( &player.mdl.sk, player.visual_transform );
 
+   skeleton_apply_transform( &player.mdl.sk, player.visual_transform );
    skeleton_debug( &player.mdl.sk );
+#endif
 }
 
 VG_STATIC void player_animate_death_cam(void)
@@ -389,22 +326,22 @@ VG_STATIC void player_animate_camera_thirdperson(void)
    static v3f lerp_cam = { 0.0f, 0.0f, 0.0f };
    v3f target;
    
-   v3_muladds( player.phys.rb.co, player.phys.rb.up, 1.2f, target );
+   v3_muladds( player.rb.co, player.rb.up, 1.2f, target );
 
    player_animate_follow_cam( target, 1.5f, 20.0f );
 }
 
 VG_STATIC void player_animate_camera(void)
 {
-   struct player_phys *phys = &player.phys;
-
    static v3f lerp_cam = {0.0f,0.0f,0.0f};
    v3f cam_pos;
 
-   player.fonboard = vg_lerpf( player.fonboard, phys->on_board, vg.time_delta );
+   int _on_board = player.controller == k_player_controller_skate;
+   player.fonboard = vg_lerpf( player.fonboard, _on_board, vg.time_delta );
 
-   if( phys->on_board )
+   if( _on_board )
    {
+#if 0
       v3f offs = { -0.4f, 0.15f, 0.0f };
       v3_lerp( lerp_cam, player.mdl.cam_pos, 0.8f, lerp_cam );
       v3_add( lerp_cam, offs, cam_pos );
@@ -412,7 +349,9 @@ VG_STATIC void player_animate_camera(void)
       /* Look angles */
       v3_lerp( phys->vl, phys->rb.v, 0.05f, phys->vl );
 
-      player.fgrind = vg_lerpf( player.fgrind, phys->grind, vg.time_delta );
+      int _grind = player_skate.activity == k_skate_activity_grind;
+
+      player.fgrind = vg_lerpf( player.fgrind, _grind, vg.time_delta );
 
       float yaw = atan2f(  phys->vl[0], -phys->vl[2] ),
           pitch = atan2f
@@ -439,11 +378,12 @@ VG_STATIC void player_animate_camera(void)
 
       v2_muladds( player.angles, shake_damp, 0.1f, player.angles );
       m4x3_mulv( player.visual_transform, cam_pos, player.camera_pos );
+#endif
    }
    else
    {
-      float speed = vg.time_delta * k_look_speed;
 #if 0
+      float speed = vg.time_delta * k_look_speed;
       player.angles[0] += vg_get_axis( "lookh" ) * speed;
       player.angles[1] += vg_get_axis( "lookv" ) * speed;
       
@@ -465,7 +405,10 @@ VG_STATIC void player_animate_camera(void)
 
       m4x3_mulv( mtx, player.mdl.cam_pos, cam_pos );
       v3_add( cam_pos, forward_dir, player.camera_pos );
+
+#if 0
       v3_lerp( phys->vl, phys->rb.v, 18.0f*vg.time_delta, phys->vl );
+#endif
    }
 }
 
index 8e5c0d2c83e7affb77a30396380bde604e7e05f1..bfd93c4af0446f0174f53f3e9882d6542c960317 100644 (file)
  */
 VG_STATIC void player_audio(void)
 {
+#if 0
    struct player_phys *phys = &player.phys;
 
    static int _air = 0;
 
    int l2 = _air;
-   _air = phys->in_air;
+   _air = player_skate.activity == k_skate_activity_air;
 
    static double last_revert = -2000.0;
 
@@ -27,7 +28,8 @@ VG_STATIC void player_audio(void)
    audio_lock();
 
    double revert_delta = vg.time - last_revert;
-   if( phys->on_board && (!_air && l2) && (revert_delta > 0.7) &&
+   if( (phys->controller == k_player_controller_skate) && 
+         (!_air && l2) && (revert_delta > 0.7) &&
        (player.air_time > 0.5f) )
    {
       audio_player_set_position( &audio_player_extra, phys->rb.co );
@@ -47,7 +49,8 @@ VG_STATIC void player_audio(void)
    }
 
    static float air = 0.0f;
-   air = vg_lerpf( air, phys->in_air? 1.0f: 0.0f, 5.0f*vg.time_delta );
+   air = vg_lerpf( air, player_skate.activity == k_skate_activity_air, 
+                        5.0f*vg.time_delta );
 
    /* Spacial info */
    v3f ears = { 1.0f,0.0f,0.0f };
@@ -110,7 +113,8 @@ VG_STATIC void player_audio(void)
       }
    }
    
-   if( freecam || player.is_dead || !phys->on_board )
+   if( freecam || player.is_dead || 
+         !(phys->controller != k_player_controller_skate))
    {
       audio_player_set_vol( &audio_player0, 0.0f );
       audio_player_set_vol( &audio_player1, 0.0f );
@@ -124,7 +128,8 @@ VG_STATIC void player_audio(void)
       else
          walk_phase = 0;
 
-      if( (player.step_phase != walk_phase) && !phys->in_air )
+      if( (player.step_phase != walk_phase) && 
+            !(player_walk.activity == k_walk_activity_air) )
       {
          audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
          audio_player_set_position( &audio_player_extra, phys->rb.co );
@@ -161,7 +166,7 @@ VG_STATIC void player_audio(void)
       float speed = vg_minf(v3_length( phys->rb.v )*0.1f,1.0f),
             attn  = speed,
             slide = vg_clampf( fabsf(phys->slip), 0.0f, 1.0f ),
-            grind = phys->grind,
+            grind = player_skate.activity == k_skate_activity_grind,
             vol0  = (1.0f-air)*attn*(1.0f-slide)*(1.0f-grind),
             vol1  =       air *attn*(1.0f-grind),
             vol2  = (1.0f-air)*attn*slide*(1.0f-grind),
@@ -183,11 +188,11 @@ VG_STATIC void player_audio(void)
 #endif
    audio_unlock();
 
-   if( player.phys.in_air )
+   if( player_skate.activity == k_skate_activity_air )
       player.air_time += vg.time_delta;
    else
       player.air_time = 0.0f;
-   
+#endif
 }
 
 #endif /* PLAYER_AUDIO_H */
diff --git a/player_device_walk.h b/player_device_walk.h
new file mode 100644 (file)
index 0000000..0559acd
--- /dev/null
@@ -0,0 +1,324 @@
+#ifndef PLAYER_DEVICE_WALK_H
+#define PLAYER_DEVICE_WALK_H
+
+#include "player_interface.h"
+
+VG_STATIC float
+   k_walkspeed             = 10.0f,
+   k_airspeed              = 2.0f,
+   k_stopspeed             = 4.0f,
+   k_walk_accel            = 10.0f,
+   k_walk_air_accel        = 7.0f,
+   k_walk_friction         = 10.0f,
+   k_walk_step_height      = 0.2f;
+
+struct player_device_walk
+{
+   rb_capsule collider;
+
+   struct
+   {
+      v3f angles;
+
+      enum walk_activity
+      {
+         k_walk_activity_air,
+         k_walk_activity_ground,
+         k_walk_activity_sleep
+      }
+      activity;
+   }
+   state;
+
+   enum mdl_surface_prop surface;
+};
+
+VG_STATIC void player_walk_pre_update( player_interface *player,
+                                       player_attachment *at )
+{
+   struct player_device_walk *w = at->storage;
+   player_look( player, w->state.angles );
+
+#if 0
+   v3f walk = {  player->input_walkh->axis.value,
+                 0.0f, 
+                -player->input_walkv->axis.value };
+
+   v3_muls( walk, 10.0f * vg.time_delta, walk );
+
+   m3x3f m;
+   euler_m3x3( w->angles, m );
+   v3_muladds( player->rb.co, m[0], walk[0], player->rb.co );
+   v3_muladds( player->rb.co, m[1], walk[1], player->rb.co );
+   v3_muladds( player->rb.co, m[2], walk[2], player->rb.co );
+#endif
+}
+
+VG_STATIC int player_walk_normal_standable( v3f n )
+{
+   return n[1] > 0.70710678118f;
+}
+
+VG_STATIC void player_accelerate( v3f v, v3f movedir, float speed, float accel )
+{
+   float currentspeed = v3_dot( v, movedir ),
+         addspeed     = speed - currentspeed;
+
+   if( addspeed <= 0 )
+      return;
+
+   float accelspeed = accel * k_rb_delta * speed;
+
+   if( accelspeed > addspeed )
+      accelspeed = addspeed;
+
+   v3_muladds( v, movedir, accelspeed, v );
+}
+
+VG_STATIC void player_friction( v3f v )
+{
+   float speed = v3_length( v ),
+         drop  = 0.0f,
+         control = vg_maxf( speed, k_stopspeed );
+
+   if( speed < 0.04f )
+      return;
+
+   drop += control * k_walk_friction * k_rb_delta;
+
+   float newspeed = vg_maxf( 0.0f, speed - drop );
+   newspeed /= speed;
+
+   v3_muls( v, newspeed, v );
+}
+
+VG_STATIC void player_walk_update( player_interface *player,
+                                   player_attachment *at )
+{
+   struct player_device_walk *w = at->storage;
+   w->collider.height = 2.0f;
+   w->collider.radius = 0.3f;
+
+   m4x3f mtx;
+   m3x3_identity( mtx );
+   v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
+
+   debug_capsule( mtx, w->collider.radius, w->collider.height, VG__WHITE );
+
+   rb_ct manifold[64];
+   int len;
+
+   float yaw = w->state.angles[0];
+
+   v3f forward_dir = { sinf(yaw),          0.0f, -cosf(yaw) };
+   v3f right_dir   = { -forward_dir[2],    0.0f,  forward_dir[0] };
+
+   v2f walk = { player->input_walkh->axis.value,
+                player->input_walkv->axis.value };
+
+   if( v2_length2(walk) > 0.001f )
+      v2_normalize_clamp( walk );
+
+   /* 
+    * Collision detection
+    */
+   len = rb_capsule__scene( mtx, &w->collider, NULL, 
+                            &world.rb_geo.inf.scene, manifold );
+   rb_manifold_filter_coplanar( manifold, len, 0.01f );
+   len = rb_manifold_apply_filtered( manifold, len );
+
+   v3f surface_avg = { 0.0f, 0.0f, 0.0f };
+   w->state.activity = k_walk_activity_air;
+
+   for( int i=0; i<len; i++ )
+   {
+      struct contact *ct = &manifold[i];
+      rb_debug_contact( ct );
+
+      if( player_walk_normal_standable( ct->n ) )
+      {
+         w->state.activity = k_walk_activity_ground;
+         v3_add( surface_avg, ct->n, surface_avg );
+      }
+
+      rb_prepare_contact( ct );
+   }
+   
+
+   /* 
+    * Move & Friction
+    */
+   float accel_speed = 0.0f, nominal_speed = 0.0f;
+   v3f movedir;
+   v3_muls( right_dir,   walk[0], movedir );
+   v3_muladds( movedir, forward_dir, walk[1], movedir );
+
+   if( w->state.activity == k_walk_activity_ground )
+   {
+      v3_normalize( surface_avg );
+
+      v3f tx, ty;
+      rb_tangent_basis( surface_avg, tx, ty );
+
+      if( v2_length2(walk) > 0.001f )
+      {
+         /* clip movement to the surface */
+         float d = v3_dot(surface_avg,movedir);
+         v3_muladds( movedir, surface_avg, -d, movedir );
+      }
+
+      accel_speed = k_walk_accel;
+      nominal_speed = k_walkspeed;
+
+      /* jump */
+      if( player->input_jump->button.value )
+      {
+         player->rb.v[1] = 5.0f;
+         w->state.activity = k_walk_activity_air;
+         accel_speed = k_walk_air_accel;
+         nominal_speed = k_airspeed;
+      }
+      else
+      {
+         player_friction( player->rb.v );
+
+         struct world_material *surface_mat = world_contact_material(manifold);
+         w->surface = surface_mat->info.surface_prop;
+      }
+   }
+   else
+   {
+      accel_speed = k_walk_air_accel;
+      nominal_speed = k_airspeed;
+   }
+
+   if( v2_length2(walk) > 0.001f )
+   {
+      vg_info( "%f %f\n", walk[0], walk[1] );
+      player_accelerate( player->rb.v, movedir, nominal_speed, accel_speed );
+      v3_normalize( movedir );
+   }
+   
+   /*
+    * Resolve velocity constraints
+    */
+   for( int j=0; j<5; j++ )
+   {
+      for( int i=0; i<len; i++ )
+      {
+         struct contact *ct = &manifold[i];
+         
+         /*normal */
+         float vn = -v3_dot( player->rb.v, ct->n );
+
+         float temp = ct->norm_impulse;
+         ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
+         vn = ct->norm_impulse - temp;
+
+         v3_muladds( player->rb.v, ct->n, vn, player->rb.v );
+      }
+   }
+
+   /* 
+    * Depenetrate
+    */
+   v3f dt;
+   v3_zero( dt );
+   for( int j=0; j<8; j++ )
+   {
+      for( int i=0; i<len; i++ )
+      {
+         struct contact *ct = &manifold[i];
+
+         float resolved_amt = v3_dot( ct->n, dt ),
+               remaining    = (ct->p-k_penetration_slop) - resolved_amt,
+               apply        = vg_maxf( remaining, 0.0f ) * 0.3f;
+
+         v3_muladds( dt, ct->n, apply, dt );
+      }
+   }
+   v3_add( dt, player->rb.co, player->rb.co );
+
+   /* TODO: Stepping......
+    *
+    *        ideas; walkgrid style steps
+    */
+#if 0
+   if( w->state.activity == k_walk_activity_ground )
+   {
+      /* step */
+      float max_dist = 0.4f;
+
+      v3f pa, pb;
+      v3_copy( player->rb.co, pa );
+      pa[1] += w->collider.radius + max_dist;
+
+      v3_muladds( pa, (v3f){0.0f,1.0f,0.0f}, -max_dist * 2.0f, pb );
+      vg_line( pa, pb, 0xff000000 );
+
+      v3f n;
+      float t;
+      if( spherecast_world( pa, pb, w->collider.radius, &t, n ) != -1 )
+      {
+         if( player_walk_normal_standable( n ) )
+         {
+            v3_lerp( pa, pb, t, player->rb.co );
+            player->rb.co[1] -= w->collider.radius;
+         }
+      }
+   }
+#endif
+
+
+   /* integrate */
+   if( w->state.activity == k_walk_activity_air )
+      player->rb.v[1] += -k_gravity * k_rb_delta;
+
+   v3_muladds( player->rb.co, player->rb.v, k_rb_delta, player->rb.co );
+
+
+   v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
+   debug_capsule( mtx, w->collider.radius, w->collider.height, VG__GREEN );
+}
+
+VG_STATIC void player_walk_post_update( player_interface *player,
+                                        player_attachment *at )
+{
+   
+}
+
+VG_STATIC void player_walk_get_camera( player_interface *player,
+                                       player_attachment *at, camera *cam )
+{
+   struct player_device_walk *w = at->storage;
+
+   float substep = vg_clampf( vg.accumulator / k_rb_delta, 0.0f, 1.0f );
+   v3f pos;
+   v3_muladds( player->rb.co, player->rb.v, k_rb_delta*substep, pos );
+
+   v3_add( pos, (v3f){0.0f,1.8f,0.0f}, cam->pos );
+   v3_copy( w->state.angles, cam->angles );
+   cam->fov = 90.0f;
+}
+
+VG_STATIC void player_walk_ui( player_interface *player,
+                               player_attachment *at )
+{
+   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_device player_device_walk =
+{
+   .pre_update = player_walk_pre_update,
+   .update = player_walk_update,
+   .post_update = player_walk_post_update,
+   .get_camera = player_walk_get_camera,
+   .debug_ui    = player_walk_ui
+};
+
+#endif /* PLAYER_DEVICE_WALK_H */
diff --git a/player_interface.h b/player_interface.h
new file mode 100644 (file)
index 0000000..c890166
--- /dev/null
@@ -0,0 +1,263 @@
+#ifndef PLAYER_INTERFACE_H
+#define PLAYER_INTERFACE_H
+
+#include "model.h"
+#include "camera.h"
+#include "rigidbody.h"
+#include "world.h"
+
+typedef struct player_device     player_device;
+typedef struct player_interface  player_interface;
+typedef struct player_attachment player_attachment;
+typedef mdl_keyframe player_pose[32];
+
+struct player_interface
+{
+   rigidbody rb;
+   camera cam;
+   
+   struct player_attachment
+   {
+      player_device *device;
+      void          *storage;
+   }
+   dev;
+
+   struct input_binding *input_js1h,
+                        *input_js1v,
+                        *input_js2h,
+                        *input_js2v,
+                        *input_jump,
+                        *input_push,
+                        *input_walk,
+                        *input_walkh,
+                        *input_walkv,
+                        *input_use,
+                        *input_reset,
+                        *input_grab;
+};
+
+struct player_device
+{
+   void (* pre_update) ( player_interface *player, player_attachment *at );
+   void (* update)     ( player_interface *player, player_attachment *at );
+   void (* post_update)( player_interface *player, player_attachment *at );
+   void (* pose)       ( player_interface *player, player_attachment *at, 
+                            player_pose pose );
+
+   void (* get_camera) ( player_interface *player, player_attachment *at, 
+                            camera *cam );
+
+   void (* attatch )   ( player_interface *player, player_attachment *at, 
+                            void *storage );
+
+   void (* reset )     ( player_interface *player, player_attachment *at,
+                           struct respawn_point *spawn );
+
+   void (* store_state)( player_interface *player, player_attachment *at );
+   void (* load_state) ( player_interface *player, player_attachment *at );
+   void (* debug_ui)   ( player_interface *player, player_attachment *at );
+};
+
+VG_STATIC void player_interface_create_player( player_interface *inst )
+{
+   static int only_once = 0;
+   assert( only_once == 0 );
+   only_once ++;
+
+   inst->input_js1h = vg_create_named_input( "steer-h", k_input_type_axis );
+   inst->input_js1v = vg_create_named_input( "steer-v", k_input_type_axis );
+   inst->input_grab = vg_create_named_input( "grab",    k_input_type_axis_norm);
+   inst->input_js2h = vg_create_named_input( "grab-h",  k_input_type_axis );
+   inst->input_js2v = vg_create_named_input( "grab-v",  k_input_type_axis );
+   inst->input_jump = vg_create_named_input( "jump",    k_input_type_button );
+   inst->input_push = vg_create_named_input( "push",    k_input_type_button );
+   inst->input_walk = vg_create_named_input( "walk",    k_input_type_button );
+   inst->input_walkh= vg_create_named_input( "walk-h",  k_input_type_axis );
+   inst->input_walkv= vg_create_named_input( "walk-v",  k_input_type_axis );
+   inst->input_use  = vg_create_named_input( "use",     k_input_type_button );
+   inst->input_reset= vg_create_named_input( "reset",   k_input_type_button );
+
+   const char *default_cfg[] = 
+   {
+      "bind  steer-h gp-ls-h",
+      "bind -steer-h a",
+      "bind +steer-h d",
+
+      "bind  steer-v gp-ls-v",
+      "bind -steer-v w",
+      "bind +steer-v s",
+
+      "bind  grab gp-rt",
+      "bind +grab shift",
+      "bind  grab-h gp-rs-h",
+      "bind  grab-v gp-rs-v",
+
+      "bind jump space",
+      "bind jump gp-a",
+
+      "bind push gp-b",
+      "bind push w",
+
+      "bind walk shift",
+      "bind walk gp-ls",
+      
+      "bind  walk-h  gp-ls-h",
+      "bind  walk-v -gp-ls-v",
+      "bind +walk-h d",
+      "bind -walk-h a",
+      "bind +walk-v w",
+      "bind -walk-v s",
+
+      "bind reset gp-lb",
+      "bind reset r",
+
+      "bind use gp-y",
+      "bind use e",
+   };
+
+   for( int i=0; i<vg_list_size(default_cfg); i++ )
+      vg_execute_console_input(default_cfg[i]);
+
+   v3_zero( inst->rb.co );
+   v3_zero( inst->rb.w );
+   v3_zero( inst->rb.v );
+   q_identity( inst->rb.q );
+   m4x3_identity( inst->rb.to_world );
+   m4x3_identity( inst->rb.to_local );
+}
+
+VG_STATIC void player_use_device( player_interface *player, player_device *dev,
+                                  void *storage )
+{
+   player->dev.device = dev;
+   player->dev.storage = storage;
+}
+
+VG_STATIC void player_pre_update( player_interface *player )
+{
+   assert( player->dev.device );
+
+   if( player->dev.device->pre_update )
+      player->dev.device->pre_update( player, &player->dev );
+}
+
+VG_STATIC void player_update( player_interface *player )
+{
+   assert( player->dev.device );
+
+   if( player->dev.device->update )
+      player->dev.device->update( player, &player->dev );
+}
+
+VG_STATIC void player_post_update( player_interface *player )
+{
+   assert( player->dev.device );
+
+   if( player->dev.device->post_update )
+      player->dev.device->post_update( player, &player->dev );
+   
+   if( player->dev.device->get_camera )
+      player->dev.device->get_camera( player, &player->dev, &player->cam );
+#if 0
+   camera_update_transform( &player->cam );
+   camera_update_view( &player->cam );
+   camera_update_projection( &player->cam );
+   camera_finalize( &player->cam );
+#endif
+}
+
+#if 0
+VG_STATIC void player_pre_render( player_interface *player )
+{
+   assert( player->dev.device );
+
+   if( player->dev.device->pre_render )
+      player->dev.device->pre_render( player );
+}
+#endif
+
+VG_STATIC void player_debugtext( int size, const char *fmt, ... )
+{
+       char buffer[ 1024 ];
+
+   va_list args;
+   va_start( args, fmt );
+   vsnprintf( buffer, 1024, fmt, args );
+   va_end( args );
+
+   ui_text( vg_uictx.cursor, buffer, size, k_text_align_right );
+       vg_uictx.cursor[1] += 14*size;
+}
+
+VG_STATIC void player_ui( player_interface *player )
+{
+   /* TODO: if debugger enabled */
+
+   if( player->dev.device->debug_ui )
+   {
+      vg_uictx.cursor[0] = vg.window_x - 200;
+      vg_uictx.cursor[1] = 0;
+      vg_uictx.cursor[2] = 0;
+      vg_uictx.cursor[3] = 200;
+
+      struct ui_vert *b = ui_fill_rect( vg_uictx.cursor, 0x70000000 );
+
+      player->dev.device->debug_ui( player, &player->dev );
+
+      b[2].co[1] = vg_uictx.cursor[1];
+      b[3].co[1] = vg_uictx.cursor[1];
+   }
+}
+
+VG_STATIC void player_spawn( player_interface *player, 
+                             struct respawn_point *rp )
+{
+   v3_copy( rp->co, player->rb.co );
+   v3_zero( player->rb.v );
+   v3_zero( player->rb.w );
+   q_identity( player->rb.q );
+}
+
+/*
+ * Apply per render-frame mouse look from player to angles
+ */
+VG_STATIC void player_look( player_interface *player, v3f angles )
+{
+   angles[2] = 0.0f;
+   v2_muladds( angles, vg.mouse_delta, 0.0025f, angles );
+
+   if( vg_input.controller_should_use_trackpad_look )
+   {
+      static v2f last_input;
+      static v2f vel;
+      static v2f vel_smooth;
+
+      v2f input = { player->input_js2h->axis.value,
+                    player->input_js2v->axis.value };
+
+      if( (v2_length2(last_input) > 0.001f) && (v2_length2(input) > 0.001f) )
+      {
+         v2_sub( input, last_input, vel );
+         v2_muls( vel, 1.0f/vg.time_delta, vel );
+      }
+      else
+      {
+         v2_zero( vel );
+      }
+
+      v2_lerp( vel_smooth, vel, vg.time_delta*8.0f, vel_smooth );
+      
+      v2_muladds( angles, vel_smooth, vg.time_delta, angles );
+      v2_copy( input, last_input );
+   }
+   else
+   {
+      angles[0] += player->input_js2h->axis.value * vg.time_delta * 4.0f;
+      angles[1] += player->input_js2v->axis.value * vg.time_delta * 4.0f;
+   }
+
+   angles[1] = vg_clampf( angles[1], -VG_PIf*0.5f, VG_PIf*0.5f );
+}
+
+#endif /* PLAYER_INTERFACE_H */
index 273fe578fdc96c000e6d9d5f49797daab69eb2b7..4a09b90589233e2c3d91a6f2cd3c81bde26abb34 100644 (file)
@@ -5,7 +5,14 @@
 #ifndef CHARACTER_H 
 #define CHARACTER_H
 
+#define VG_GAME
+#include "vg/vg.h"
+
+#if 0
 #include "player.h"
+#endif
+
+#include "model.h"
 #include "player_ragdoll.h"
 #include "shaders/viewchar.h"
 
index 31590d8fb36bb34aab8c1cf75b67b8045b4837af..56d793c553fb29dea868d59b43e437f76ae8d7f6 100644 (file)
 /*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
 
-#ifndef PLAYER_PHYSICS_H
-#define PLAYER_PHYSICS_H
-
-#include "player.h"
-#include "camera.h"
-
-VG_STATIC void apply_gravity( v3f vel, float const timestep )
-{
-   v3f gravity = { 0.0f, -9.6f, 0.0f };
-   v3_muladds( vel, gravity, timestep, vel );
-}
-
-VG_STATIC struct 
-grind_edge *player_grind_collect_edge( v3f p0, v3f p1,
-                                       v3f c0, v3f c1, float max_dist )
-{
-   struct player_phys *phys = &player.phys;
-
-   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;
-}
-
-/*
- * 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 );
-
-   v3f dir_inv;
-   dir_inv[0] = 1.0f/dir[0];
-   dir_inv[1] = 1.0f/dir[1];
-   dir_inv[2] = 1.0f/dir[2];
-
-   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_aabb1( box, pa, dir_inv, 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
- */
-VG_STATIC void player_start_air(void)
-{
-   struct player_phys *phys = &player.phys;
-
-   float pstep = VG_TIMESTEP_FIXED * 10.0f;
-   float best_velocity_delta = -9999.9f;
-
-   v3f axis;
-   v3_cross( phys->rb.up, phys->rb.v, axis );
-   v3_normalize( axis );
-   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_prediction *p = 
-         &player.predictions[ player.prediction_count ++ ];
-
-      float vmod = ((float)m / 15.0f)*0.09f;
-
-      m3x3f vr;
-      v4f vr_q;
-
-      q_axis_angle( vr_q, axis, vmod );
-      q_m3x3( vr_q, vr );
-
-      player_predict_land( vr, p );
-
-      if( p->type != k_prediction_none )
-      {
-         if( p->score < min_score )
-         {
-            min_score = p->score;
-            best_vmod = vmod;
-         }
-
-         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, phys->vr );
-
-   q_axis_angle( vr_q, axis, best_vmod );
-   q_m3x3( vr_q, phys->vr_pstep );
-
-   /*
-    * Logging
-    */
-   for( int i=0; i<player.prediction_count; i ++ )
-   {
-      struct land_prediction *p = &player.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;
-   }
-}
-
-
-VG_STATIC void player_physics_control_passive(void)
-{
-   struct player_phys *phys = &player.phys;
-   float grabt = player.input_grab->axis.value;
-
-   if( grabt > 0.5f )
-   {
-      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 );
-
-   phys->grab = vg_lerpf( phys->grab, grabt, 0.14f );
-   player.phys.pushing = 0.0f;
-
-   if( !phys->jump_charge || phys->in_air )
-   {
-      phys->jump -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
-   }
-
-   phys->jump_charge = 0;
-   phys->jump = vg_clampf( phys->jump, 0.0f, 1.0f );
-}
-
-/*
- * Main friction interface model
- */
-VG_STATIC void player_physics_control(void)
-{
-   struct player_phys *phys = &player.phys;
-
-   /*
-    * Computing localized friction forces for controlling the character
-    * Friction across X is significantly more than Z
-    */
-
-   v3f vel;
-   m3x3_mulv( phys->rb.to_local, phys->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;
-   phys->slip = slip;
-   phys->reverse = -vg_signf(vel[2]);
-
-   float substep = VG_TIMESTEP_FIXED;
-   float fwd_resistance = k_friction_resistance;
-
-   vel[2] = stable_force( vel[2],vg_signf(vel[2]) * -fwd_resistance*substep);
-   vel[0] = stable_force( vel[0],vg_signf(vel[0]) * -k_friction_lat*substep);
-   
-   if( player.input_jump->button.value )
-   {
-      phys->jump += VG_TIMESTEP_FIXED * k_jump_charge_speed;
-
-      if( !phys->jump_charge )
-         phys->jump_dir = phys->reverse > 0.0f? 1: 0;
-
-      phys->jump_charge = 1;
-   }
-
-   static int push_thresh_last = 0;
-   float push = player.input_push->button.value;
-   int push_thresh = push>0.15f? 1: 0;
-   
-   if( push_thresh && !push_thresh_last )
-      player.phys.start_push = vg.time;
-
-   push_thresh_last = push_thresh;
-
-   if( !player.input_jump->button.value && push_thresh )
-   {
-      player.phys.pushing = 1.0f;
-      player.phys.push_time = vg.time - player.phys.start_push;
-
-      float cycle_time = player.phys.push_time*k_push_cycle_rate,
-            amt = k_push_accel * (sinf(cycle_time)*0.5f+0.5f)*VG_TIMESTEP_FIXED,
-            current = v3_length( vel ),
-            new_vel = vg_minf( current + amt, k_max_push_speed );
-
-      new_vel -= vg_minf(current, k_max_push_speed);
-      vel[2] -= new_vel * phys->reverse;
-   }
-
-   m3x3_mulv( phys->rb.to_world, vel, phys->rb.v );
-   
-   float input = player.input_js1h->axis.value,
-         grab  = player.input_grab->axis.value,
-         steer = input * (1.0f-(phys->jump+grab)*0.4f),
-         steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
-
-   phys->iY -= steer_scaled * VG_TIMESTEP_FIXED;
-   
-
-   /*
-    * EXPERIMENTAL
-    * ===============================================
-    */
-#if 0
-   v3f cog_ideal, diff;
-
-   v3_muladds( phys->rb.co, phys->rb.up, 1.0f-grab, cog_ideal );
-   v3_sub( cog_ideal, phys->cog, diff );
-
-   /* temp */
-   if( v3_length2( diff ) > 20.0f*20.0f )
-      v3_copy( cog_ideal, phys->cog );
-   else
-   {
-      float rate_lat  = k_cog_spring_lat * VG_TIMESTEP_FIXED,
-            rate_vert = k_cog_spring_vert * VG_TIMESTEP_FIXED,
-            vert_amt  = v3_dot( diff, phys->rb.up );
-
-      /* Split into vert/lat springs */
-      v3f diff_vert, diff_lat;
-      v3_muladds( diff, phys->rb.up, -vert_amt, diff_lat );
-      v3_muls( phys->rb.up, vert_amt, diff_vert );
-
-
-      if( diff[1] > 0.0f )
-         rate_vert *= k_cog_boost_multiplier;
-
-      float ap_a =        k_cog_mass_ratio,
-            ap_b = -(1.0f-k_cog_mass_ratio);
-
-      v3_muladds( phys->cog_v, diff_lat,  rate_lat  * ap_a,  phys->cog_v );
-      v3_muladds( phys->cog_v, diff_vert, rate_vert * ap_a,  phys->cog_v );
-
-      //v3_muladds( phys->rb.v,  diff_lat,  rate_lat  * ap_b,  phys->rb.v );
-      v3_muladds( phys->rb.v,  diff_vert, rate_vert * ap_b,  phys->rb.v );
-
-      /* dampen */
-      v3_muls( phys->cog_v, 1.0f-(VG_TIMESTEP_FIXED*k_cog_damp), phys->cog_v );
-
-      /* integrate */
-      v3_muladds( phys->cog, phys->cog_v, VG_TIMESTEP_FIXED, phys->cog );
-   }
-
-
-   /*
-    * EXPERIMENTAL
-    * ===============================================
-    */
-#endif
-
-
-   if( !phys->jump_charge && phys->jump > 0.2f )
-   {
-      v3f jumpdir;
-      
-      /* Launch more up if alignment is up else improve velocity */
-      float aup = fabsf(v3_dot( (v3f){0.0f,1.0f,0.0f}, phys->rb.up )),
-            mod = 0.5f,
-            dir = mod + aup*(1.0f-mod);
-
-      v3_copy( phys->rb.v, jumpdir );
-      v3_normalize( jumpdir );
-      v3_muls( jumpdir, 1.0f-dir, jumpdir );
-      v3_muladds( jumpdir, phys->rb.up, dir, jumpdir );
-      v3_normalize( jumpdir );
-      
-      float force = k_jump_force*phys->jump;
-      v3_muladds( phys->rb.v, jumpdir, force, phys->rb.v );
-      phys->jump = 0.0f;
-
-      player.jump_time = vg.time;
-      
-      /* TODO: Move to audio file */
-      audio_lock();
-      audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
-      audio_player_set_position( &audio_player_extra, phys->rb.co );
-      audio_player_set_vol( &audio_player_extra, 20.0f );
-      audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
-      audio_unlock();
-   }
-}
-
-VG_STATIC void player_physics_control_grind(void)
-{
-   struct player_phys *phys = &player.phys;
-   v2f steer = { player.input_js1h->axis.value,
-                 player.input_js1v->axis.value };
-
-   float l2 = v2_length2( steer );
-   if( l2 > 1.0f )
-      v2_muls( steer, 1.0f/sqrtf(l2), steer );
-
-   phys->iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
-
-   float iX = steer[1] * phys->reverse * k_steer_air * VG_TIMESTEP_FIXED;
-
-   static float siX = 0.0f;
-   siX = vg_lerpf( siX, iX, k_steer_air_lerp );
-   
-   v4f rotate;
-   q_axis_angle( rotate, phys->rb.right, siX );
-   q_mul( rotate, phys->rb.q, phys->rb.q );
-
-   phys->slip = 0.0f;
-}
-
-/*
- * Air control, no real physics
- */
-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 );
-
-   ray_hit hit;
-
-   /* 
-    * Prediction 
-    */
-   float pstep  = VG_TIMESTEP_FIXED * 1.0f;
-   float k_bias = 0.98f;
-
-   v3f pco, pco1, pv;
-   v3_copy( phys->rb.co, pco );
-   v3_muls( phys->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( phys->vr, pv, pv );
-      apply_gravity( pv, 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 = 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 ) )
-      {
-         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( 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 };
-
-   float l2 = v2_length2( steer );
-   if( l2 > 1.0f )
-      v2_muls( steer, 1.0f/sqrtf(l2), steer );
-
-   phys->iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
-
-   float iX = steer[1] * 
-              phys->reverse * k_steer_air * limiter * VG_TIMESTEP_FIXED;
-
-   static float siX = 0.0f;
-   siX = vg_lerpf( siX, iX, k_steer_air_lerp );
-   
-   v4f rotate;
-   q_axis_angle( rotate, phys->rb.right, siX );
-   q_mul( rotate, phys->rb.q, phys->rb.q );
-   
-#if 0
-   v2f target = {0.0f,0.0f};
-   v2_muladds( target, (v2f){ vg_get_axis("grabh"), vg_get_axis("grabv") },
-               phys->grab, target );
-#endif
-}
-
-VG_STATIC void player_walk_collider_configuration(void)
-{
-   struct player_phys *phys = &player.phys;
-   float h0 = 0.3f,
-         h1 = 0.9f;
-
-   rigidbody *rbf = &player.collide_front,
-             *rbb = &player.collide_back;
-
-   v3_add( phys->rb.co, (v3f){0.0f,h0,0.0f}, rbf->co );
-   v3_add( phys->rb.co, (v3f){0.0f,h1,0.0f}, rbb->co );
-   v3_copy( rbf->co, rbf->to_world[3] );
-   v3_copy( rbb->co, rbb->to_world[3] );
-   m4x3_invert_affine( rbf->to_world, rbf->to_local );
-   m4x3_invert_affine( rbb->to_world, rbb->to_local );
-
-   rb_update_bounds( rbf );
-   rb_update_bounds( rbb );
-}
-
-VG_STATIC void player_regular_collider_configuration(void)
-{
-   struct player_phys *phys = &player.phys;
-   
-   /* Standard ground configuration */
-   rigidbody *rbf = &player.collide_front,
-             *rbb = &player.collide_back;
-
-   m3x3_copy( phys->rb.to_world, player.collide_front.to_world );
-   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.0f;
-
-   m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h,-k_board_length}, rbf->co );
-   v3_copy( rbf->co, rbf->to_world[3] );
-   m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h, k_board_length}, rbb->co );
-   v3_copy( rbb->co, rbb->to_world[3] );
-
-   m4x3_invert_affine( rbf->to_world, rbf->to_local );
-   m4x3_invert_affine( rbb->to_world, rbb->to_local );
-
-   rb_update_bounds( rbf );
-   rb_update_bounds( rbb );
-}
-
-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;
-      }
-   }
-}
-
-VG_STATIC int player_update_collision_manifold( rb_ct *manifold );
-VG_STATIC void player_walk_physics(void)
-{
-   struct player_phys *phys = &player.phys;
-   rigidbody *rbf = &player.collide_front,
-             *rbb = &player.collide_back;
-
-   m3x3_identity( player.collide_front.to_world );
-   m3x3_identity( player.collide_back.to_world );
-
-   v3_zero( phys->rb.w );
-   q_axis_angle( phys->rb.q, (v3f){0.0f,1.0f,0.0f}, -player.angles[0] );
-
-   rb_ct manifold[64];
-   int len;
-
-   v3f forward_dir = { sinf(player.angles[0]),0.0f,-cosf(player.angles[0]) };
-   v3f right_dir = { -forward_dir[2], 0.0f, forward_dir[0] };
-
-   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 );
-
-   if( phys->in_air )
-   {
-      player_walk_collider_configuration();
-
-      /* allow player to accelerate a bit */
-      v3f walk_3d;
-      v3_muls( forward_dir, walk[1], walk_3d );
-      v3_muladds( walk_3d, right_dir, walk[0], walk_3d );
-
-      float current_vel = fabsf(v3_dot( walk_3d, phys->rb.v )),
-            new_vel     = current_vel + VG_TIMESTEP_FIXED*k_air_accelerate,
-            clamped_new = vg_clampf( new_vel, 0.0f, k_walkspeed ),
-            vel_diff    = vg_maxf( 0.0f, clamped_new - current_vel );
-
-      v3_muladds( phys->rb.v, right_dir,   walk[0] * vel_diff, phys->rb.v );
-      v3_muladds( phys->rb.v, forward_dir, walk[1] * vel_diff, phys->rb.v );
-
-
-      len = player_update_collision_manifold( manifold );
-      rb_presolve_contacts( manifold, len );
-
-      for( int i=0; i<len; i++ )
-      {
-         struct contact *ct = &manifold[i];
-         if( v3_dot( ct->n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f )
-            phys->in_air = 0;
-      }
-
-      for( int j=0; j<5; j++ )
-      {
-         for( int i=0; i<len; i++ )
-         {
-            struct contact *ct = &manifold[i];
-            
-            /*normal */
-            float vn = -v3_dot( phys->rb.v, 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 );
-
-            v3_add( impulse, phys->rb.v, phys->rb.v );
-
-            /* friction */
-            for( int j=0; j<2; j++ )
-            {
-               float     f = k_friction * ct->norm_impulse,
-                        vt = v3_dot( phys->rb.v, ct->t[j] ),
-                    lambda = -vt;
-               
-               float temp = ct->tangent_impulse[j];
-               ct->tangent_impulse[j] = vg_clampf( temp + lambda, -f, f );
-               lambda = ct->tangent_impulse[j] - temp;
-
-               v3_muladds( phys->rb.v, ct->t[j], lambda, phys->rb.v );
-            }
-         }
-      }
-
-      player_integrate();
-   }
-   else
-   {
-      player.walk = v2_length( walk );
-
-      if( player.input_walk->button.value )
-         v2_muls( walk, 0.5f, walk );
-
-      v2_muls( walk, k_walkspeed * VG_TIMESTEP_FIXED, walk );
-
-      /* Do XY translation */
-      v3f walk_apply, walk_measured;
-      v3_zero( walk_apply );
-      v3_muladds( walk_apply, right_dir,   walk[0], walk_apply );
-      v3_muladds( walk_apply, forward_dir, walk[1], walk_apply );
-      v3_add( walk_apply, phys->rb.co, phys->rb.co );
-
-      /* Directly resolve collisions */
-      player_walk_collider_configuration();
-      len = player_update_collision_manifold( manifold );
-
-      v3f dt;
-      v3_zero( dt );
-      for( int j=0; j<8; j++ )
-      {
-         for( int i=0; i<len; i++ )
-         {
-            struct contact *ct = &manifold[i];
-
-            float resolved_amt = v3_dot( ct->n, dt ),
-                  remaining    = (ct->p-k_penetration_slop) - resolved_amt,
-                  apply        = vg_maxf( remaining, 0.0f ) * 0.3f;
-
-            v3_muladds( dt, ct->n, apply, dt );
-         }
-      }
-      v3_add( dt, phys->rb.co, phys->rb.co );
-
-      v3_add( dt, walk_apply, walk_measured );
-      v3_divs( walk_measured, VG_TIMESTEP_FIXED, phys->rb.v );
-
-      if( len )
-      {
-         struct world_material *surface_mat = world_contact_material(manifold);
-         player.surface_prop = surface_mat->info.surface_prop;
-      }
-
-      /* jump */
-      if( player.input_jump->button.value )
-      {
-         phys->rb.v[1] = 5.0f;
-         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();
-   }
-}
-
-VG_STATIC void player_grind(void)
-{
-   struct player_phys *phys = &player.phys;
-
-   v3f closest;
-   int idx = bh_closest_point( world.grind_bh, phys->rb.co, closest, INFINITY );
-   if( idx == -1 )
-      return;
-
-   struct grind_edge *edge = &world.grind_edges[ idx ];
-
-   vg_line( phys->rb.co, closest, 0xff000000 );
-   vg_line_cross( closest, 0xff000000, 0.3f );
-   vg_line( edge->p0, edge->p1, 0xff000000 );
-
-   v3f grind_delta;
-   v3_sub( closest, phys->rb.co, grind_delta );
-   
-   float p = v3_dot( phys->rb.forward, grind_delta );
-   v3_muladds( grind_delta, phys->rb.forward, -p, grind_delta );
-   
-   float a = vg_maxf( 0.0f, 4.0f-v3_dist2( closest, phys->rb.co ) );
-   v3_muladds( phys->rb.v, grind_delta, a*0.2f, phys->rb.v );
-}
-
-VG_STATIC int player_update_grind_collision( rb_ct *contact )
-{
-   struct player_phys *phys = &player.phys;
-
-   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-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, 
-                                                                c0, c1, k_r );
-
-   
-#if 0
-   vg_line( p0, p1, 0xff0000ff );
-#endif
-
-   if( closest_edge )
-   {
-#if 0
-      vg_line_cross( c0, 0xff000000, 0.1f );
-      vg_line_cross( c1, 0xff000000, 0.1f );
-      vg_line( c0, c1, 0xff000000 );
-#endif
-
-      v3f delta;
-      v3_sub( c1, c0, delta );
-
-      if( v3_dot( delta, phys->rb.up ) > 0.0001f )
-      {
-         contact->p = v3_length( delta );
-         contact->type = k_contact_type_edge;
-         contact->element_id = 0;
-         v3_copy( c1, contact->co );
-         contact->rba = &player.phys.rb;
-         contact->rbb = &world.rb_geo;
-
-         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 );
-
-#if 0
-         vg_info( "%f %f\n", v3_length( contact->n ), contact->p );
-#endif
-
-         return 1;
-      }
-      else
-         return -1;
-   }
-
-   return 0;
-}
-
-/* Manifold must be able to hold at least 64 elements */
-VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
-{
-   struct player_phys *phys = &player.phys;
-
-   rigidbody *rbf = &player.collide_front,
-             *rbb = &player.collide_back;
-
-   rb_debug( rbf, 0xff00ffff );
-   rb_debug( rbb, 0xffffff00 );
-
-
-#if 0
-   phys->rise = vg_lerpf( phys->rise, phys->in_air? -0.25f: 0.0f, 
-                          VG_TIMESTEP_FIXED );
-#endif
-
-   int len_f = 0,
-       len_b = 0; 
+#define PLAYER_PHYSICS_H
+#ifndef PLAYER_PHYSICS_H
+#define PLAYER_PHYSICS_H
 
-   len_f = rb_sphere_scene( rbf, &world.rb_geo, manifold );
-   rb_manifold_filter_coplanar( manifold, len_f, 0.05f );
-   if( len_f > 1 )
-   {
-      rb_manifold_filter_backface( manifold, len_f );
-      rb_manifold_filter_joint_edges( manifold, len_f, 0.05f );
-      rb_manifold_filter_pairs( manifold, len_f, 0.05f );
-   }
-   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 );
-   rb_manifold_filter_coplanar( man_b, len_b, 0.05f );
-   if( len_b > 1 )
-   {
-      rb_manifold_filter_backface( man_b, len_b );
-      rb_manifold_filter_joint_edges( man_b, len_b, 0.05f );
-      rb_manifold_filter_pairs( man_b, len_b, 0.05f );
-   }
-   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;
+#include "player.h"
+#include "camera.h"
 
-   return len_f + len_b;
-}
+VG_STATIC void player_integrate(void);
 
-VG_STATIC void player_adhere_ground( rb_ct *manifold, int len )
+VG_STATIC int player_collide_sphere( rigidbody *rb, rb_ct *manifold )
 {
-   struct player_phys *phys = &player.phys;
-   int was_in_air = phys->in_air;
+   int len = 0;
 
-   v3f surface_avg;
-   v3_zero( surface_avg );
-
-   /*
-    *
-    * EXPERIMENTAL
-    * ================================================================
-    */
-   if( phys->in_air )
-      player.normal_pressure = 0.0f;
-   else
-      player.normal_pressure = v3_dot( phys->rb.up, phys->rb.v );
-
-   v3f p0_0, p0_1,
-       p1_0, p1_1,
-       n0, n1;
-   float t0, t1;
-
-   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;
-
-   v3_copy( player.collide_front.co, p0_0 );
-   v3_copy( player.collide_back.co,  p1_0 );
-
-   v3_muladds( p0_0, phys->rb.up, -disp_k, p0_1 );
-   v3_muladds( p1_0, phys->rb.up, -disp_k, p1_1 );
-
-   int cast0 = spherecast_world( p0_0, p0_1, 0.2f, &t0, n0 ),
-       cast1 = spherecast_world( p1_0, p1_1, 0.2f, &t1, n1 );
-
-   v3f animp0, animp1;
-
-   m4x3f temp;
-   m3x3_copy( phys->rb.to_world, temp );
-   if( cast0 != -1 )
-   {
-      v3_lerp( p0_0, p0_1, t0, temp[3] );
-      v3_copy( temp[3], animp0 );
-      debug_sphere( temp, 0.2f, VG__PINK );
-      
-      v3f F, delta;
-      v3_sub( p0_0, phys->rb.co, delta );
-      
-      float displacement = vg_clampf( 1.0f-t0, 0.0f, 1.0f ),
-            damp         = vg_maxf( 0.0f, v3_dot( phys->rb.up, phys->rb.v ) );
-      v3_muls( phys->rb.up, displacement*spring_k*k_rb_delta -
-                            damp*damp_K*k_rb_delta, F );
-
-      v3_muladds( phys->rb.v, F, 1.0f, phys->rb.v );
-      
-      /* Angular velocity */
-      v3f wa;
-      v3_cross( delta, F, wa );
-      v3_muladds( phys->rb.w, wa, k_spring_angular, phys->rb.w );
-   }
-   else
-      v3_copy( p0_1, animp0 );
-
-   if( cast1 != -1 )
-   {
-      v3_lerp( p1_0, p1_1, t1, temp[3] );
-      v3_copy( temp[3], animp1 );
-      debug_sphere( temp, 0.2f, VG__PINK );
-
-      v3f F, delta;
-      v3_sub( p1_0, phys->rb.co, delta );
-      
-      float displacement = vg_clampf( 1.0f-t1, 0.0f, 1.0f ),
-            damp         = vg_maxf( 0.0f, v3_dot( phys->rb.up, phys->rb.v ) );
-      v3_muls( phys->rb.up, displacement*spring_k*k_rb_delta -
-                            damp*damp_K*k_rb_delta, F );
-
-      v3_muladds( phys->rb.v, F, 1.0f, phys->rb.v );
-      
-      /* Angular velocity */
-      v3f wa;
-      v3_cross( delta, F, wa );
-      v3_muladds( phys->rb.w, wa, k_spring_angular, phys->rb.w );
-   }
-   else
-      v3_copy( p1_1, animp1 );
-
-   v3f animavg, animdelta;
-   v3_add( animp0, animp1, animavg );
-   v3_muls( animavg, 0.5f, animavg );
-
-   v3_sub( animp1, animp0, animdelta );
-   v3_normalize( animdelta );
-
-   m4x3_mulv( phys->rb.to_local, animavg, player.board_offset );
-
-   float dx = -v3_dot( animdelta, phys->rb.forward ),
-         dy =  v3_dot( animdelta, phys->rb.up );
-
-   float angle = -atan2f( dy, dx );
-   q_axis_angle( player.board_rotation, (v3f){ 1.0f, 0.0f, 0.0f }, angle );
-
-   /*
-    * ================================================================
-    * EXPERIMENTAL
-    */
-
-   if( len == 0 && !((cast0 !=-1)&&(cast1!=-1)) )
+   len = rb_sphere_scene( rb, &world.rb_geo, manifold );
+   rb_manifold_filter_coplanar( manifold, len, 0.05f );
+   if( len > 1 )
    {
-      phys->lift_frames ++;
-
-      if( phys->lift_frames >= 8 )
-         phys->in_air = 1;
+      rb_manifold_filter_backface( manifold, len );
+      rb_manifold_filter_joint_edges( manifold, len, 0.05f );
+      rb_manifold_filter_pairs( manifold, len, 0.05f );
    }
+   int new_len = rb_manifold_apply_filtered( manifold, len );
+   if( len && !new_len )
+      len = 1;
    else
-   {
-      for( int i=0; i<len; i++ )
-         v3_add( surface_avg, manifold[i].n, surface_avg );
-      v3_normalize( surface_avg );
-      
-      if( v3_dot( phys->rb.v, surface_avg ) > 0.7f )
-      {
-         phys->lift_frames ++;
-
-         if( phys->lift_frames >= 8 )
-            phys->in_air = 1;
-      }
-      else
-      {
-         phys->in_air = 0;
-         phys->lift_frames = 0;
-         v3f projected, axis;
+      len = new_len;
 
-         float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
-         v3_muladds( phys->rb.v, phys->rb.up, DOWNFORCE, phys->rb.v );
-
-         float d = v3_dot( phys->rb.forward, surface_avg );
-         v3_muladds( surface_avg, phys->rb.forward, -d, projected );
-         v3_normalize( projected );
-
-         float angle = v3_dot( phys->rb.up, projected );
-         v3_cross( phys->rb.up, projected, axis );
-
-#if 0
-         v3f p0, p1;
-         v3_add( phys->rb.co, projected, p0 );
-         v3_add( phys->rb.co, phys->rb.up, p1 );
-         vg_line( phys->rb.co, p0, 0xff00ff00 );
-         vg_line( phys->rb.co, p1, 0xff000fff );
-#endif
-
-         if( fabsf(angle) < 0.999f )
-         {
-            v4f correction;
-            q_axis_angle( correction, axis, 
-                          acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
-            q_mul( correction, phys->rb.q, phys->rb.q );
-         }
-      }
-   }
-
-   if( !was_in_air && phys->in_air )
-      player_start_air();
+   return len;
 }
 
-VG_STATIC void player_collision_response( rb_ct *manifold, int len )
-{
-   struct player_phys *phys = &player.phys;
-
-   /*
-    * EXPERIMENTAL
-    * ===============================================
-    */
-
 #if 0
-   player.normal_pressure = v3_dot( phys->rb.up, phys->rb.v );
-
-   {
-      float ideal   = 1.0f-player.input_grab->axis.value,
-            diff    = phys->spring - ideal,
-            Fspring = -k_cog_spring_lat * diff,
-            Fdamp   = -k_cog_damp       * phys->springv,
-            F       = (Fspring + Fdamp) * k_rb_delta;
-
-      phys->springv += F;
-      phys->spring  += phys->springv * k_rb_delta;
-
-      if( phys->springv > 0.0f )
-         v3_muladds( phys->rb.v, phys->rb.up, F*k_cog_spring_vert, phys->rb.v );
-
-      if( phys->in_air )
-         player.normal_pressure = 0.0f;
-      else
-         player.normal_pressure = v3_dot( phys->rb.up, phys->rb.v );
-   }
-#endif
-
-   if( player.input_grab->axis.value > 0.5f )
-   {
-      if( !phys->in_air )
-      {
-         /* Throw */
-         v3_muls( phys->rb.up, k_mmthrow_scale, phys->throw_v );
-      }
-   }
-   else
-   {
-      /* Collect */
-      float doty = v3_dot( phys->rb.up, phys->throw_v );
-      
-      v3f Fl, Fv;
-      v3_muladds( phys->throw_v, phys->rb.up, -doty, Fl );
-
-      if( !phys->in_air )
-      {
-         v3_muladds( phys->rb.v,    Fl,  k_mmcollect_lat, phys->rb.v );
-         v3_muladds( phys->throw_v, Fl, -k_mmcollect_lat, phys->throw_v );
-      }
-
-      v3_muls( phys->rb.up, -doty, Fv );
-      v3_muladds( phys->rb.v,    Fv, k_mmcollect_vert, phys->rb.v );
-      v3_muladds( phys->throw_v, Fv, k_mmcollect_vert, phys->throw_v );
-
-      v3_copy( Fl, player.debug_mmcollect_lat );
-      v3_copy( Fv, player.debug_mmcollect_vert );
-   }
-
-   /* Decay */
-   if( v3_length2( phys->throw_v ) > 0.0001f )
-   {
-      v3f dir;
-      v3_copy( phys->throw_v, dir );
-      v3_normalize( dir );
-
-      float max = v3_dot( dir, phys->throw_v ),
-            amt = vg_minf( k_mmdecay * k_rb_delta, max );
-
-      v3_muladds( phys->throw_v, dir, -amt, phys->throw_v );
-   }
-
-
-   /* TODO: RElocate */
-   {
-
-   v3f ideal_cog, ideal_diff;
-   v3_muladds( phys->rb.co, phys->rb.up, 
-               1.0f-player.input_grab->axis.value, ideal_cog );
-   v3_sub( ideal_cog, phys->cog, ideal_diff );
-
-   /* Apply velocities */
-   v3f rv;
-   v3_sub( phys->rb.v, phys->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;
-
-   v3_muladds( phys->cog_v, F, -rb, phys->cog_v );
-   }
-
-   /*
-    * EXPERIMENTAL
-    * ===============================================
-    */
-
-   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, phys->rb.co, delta ); 
-         v3_cross( phys->rb.w, delta, dv );
-         v3_add( phys->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, phys->rb.forward )) > 10.0f ||
-             fabsf(v3_dot( impulse, phys->rb.up )) > 50.0f )
-         {
-            player_kill();
-            return;
-         }
-
-         v3_add( impulse, phys->rb.v, phys->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( 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 );
-      }
-   }
-   
-   /* early integrate this */
-   phys->cog_v[1] += -9.8f * k_rb_delta;
-   v3_muladds( phys->cog, phys->cog_v, k_rb_delta, phys->cog );
-}
-
-VG_STATIC void player_save_frame(void)
-{
-   player.phys_gate_frame = player.phys;
-}
-
-VG_STATIC void player_restore_frame(void)
+VG_STATIC void apply_gravity( v3f vel, float const timestep )
 {
-   player.phys = player.phys_gate_frame;
-   rb_update_transform( &player.phys.rb );
+   v3f gravity = { 0.0f, -9.6f, 0.0f };
+   v3_muladds( vel, gravity, timestep, vel );
 }
 
 VG_STATIC void player_integrate(void)
@@ -1354,197 +49,7 @@ VG_STATIC void player_integrate(void)
    apply_gravity( phys->rb.v, VG_TIMESTEP_FIXED );
    v3_muladds( phys->rb.co, phys->rb.v, VG_TIMESTEP_FIXED, phys->rb.co );
 }
-
-VG_STATIC void player_do_motion(void)
-{
-   struct player_phys *phys = &player.phys;
-
-   if( world.water.enabled )
-   {
-      if( (phys->rb.co[1] < 0.0f) && !player.is_dead )
-      {
-         audio_lock();
-         audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
-         audio_player_set_position( &audio_player_extra, phys->rb.co );
-         audio_player_set_vol( &audio_player_extra, 20.0f );
-         audio_player_playclip( &audio_player_extra, &audio_splash );
-         audio_unlock();
-
-         player_kill();
-      }
-   }
-
-   v3f prevco;
-   v3_copy( phys->rb.co, prevco );
-
-   if( phys->on_board )
-   {
-      rb_ct manifold[72];
-      
-      player_regular_collider_configuration();
-      int len = player_update_collision_manifold( manifold );
-      int grind_col = player_update_grind_collision( &manifold[len] );
-
-      static int _grind_col_pre = 0;
-
-      if( grind_col )
-      {
-         phys->grind = 1;
-         v3f up = { 0.0f, 1.0f, 0.0f };
-         float angle = v3_dot( phys->rb.up, up );
-
-         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) );
-            q_mul( correction, phys->rb.q, phys->rb.q );
-         }
-
-         float const DOWNFORCE = -k_downforce*1.2f*VG_TIMESTEP_FIXED;
-         v3_muladds( phys->rb.v, manifold[len].n, DOWNFORCE, phys->rb.v );
-         m3x3_identity( phys->vr );
-         m3x3_identity( phys->vr_pstep );
-
-         if( !_grind_col_pre )
-         {
-            audio_lock();
-            audio_player_set_flags( &audio_player_extra, 
-                                    AUDIO_FLAG_SPACIAL_3D );
-            audio_player_set_position( &audio_player_extra, phys->rb.co );
-            audio_player_set_vol( &audio_player_extra, 20.0f );
-            audio_player_playclip( &audio_player_extra, &audio_board[5] );
-            audio_unlock();
-         }
-      }
-      else
-      {
-         phys->grind = 0;
-         player_adhere_ground( manifold, len );
-
-         if( _grind_col_pre )
-         {
-            audio_lock();
-            audio_player_set_flags( &audio_player_extra, 
-                                    AUDIO_FLAG_SPACIAL_3D );
-            audio_player_set_position( &audio_player_extra, phys->rb.co );
-            audio_player_set_vol( &audio_player_extra, 20.0f );
-            audio_player_playclip( &audio_player_extra, &audio_board[6] );
-            audio_unlock();
-         }
-      }
-
-      _grind_col_pre = grind_col;
-
-      rb_presolve_contacts( manifold, len+ VG_MAX(0,grind_col) );
-      player_collision_response( manifold, len+ VG_MAX(0,grind_col) );
-
-      player_physics_control_passive();
-
-      if( grind_col )
-      {
-         phys->in_air = 0;
-         player_physics_control_grind();
-      }
-      else
-      {
-         if( phys->in_air )
-            player_physics_control_air();
-         else
-            player_physics_control();
-      }
-
-      player_integrate();
-   }
-   else
-      player_walk_physics();
-   
-   
-   /* Real angular velocity integration */
-   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;
-      v3f axis;
-      v3_copy( phys->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, phys->rb.q, phys->rb.q );
-   }
-
-   /* Faux angular velocity */
-   v4f rotate; 
-
-   float lerpq = phys->in_air? 0.04f: 0.3f;
-   phys->siY = vg_lerpf( phys->siY, phys->iY, lerpq );
-
-   q_axis_angle( rotate, phys->rb.up, phys->siY );
-   q_mul( rotate, phys->rb.q, phys->rb.q );
-   phys->iY = 0.0f;
-
-   /* 
-    * Gate intersection, by tracing a line over the gate planes 
-    */
-   for( int i=0; i<world.gate_count; i++ )
-   {
-      struct route_gate *rg = &world.gates[i];
-      teleport_gate *gate = &rg->gate;
-
-      if( gate_intersect( gate, phys->rb.co, prevco ) )
-      {
-         m4x3_mulv( gate->transport, phys->rb.co, phys->rb.co );
-         m4x3_mulv( gate->transport, phys->cog, phys->cog );
-         m3x3_mulv( gate->transport, phys->cog_v, phys->cog_v );
-         m3x3_mulv( gate->transport, phys->rb.v, phys->rb.v );
-         m3x3_mulv( gate->transport, phys->vl, phys->vl );
-         m3x3_mulv( gate->transport, phys->v_last, phys->v_last );
-         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 );
-         
-         world_routes_activate_gate( i );
-
-         if( !phys->on_board )
-         {
-            v3f fwd_dir = {cosf(player.angles[0]),
-                           0.0f,
-                           sinf(player.angles[0])};
-            m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
-
-            player.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
-         }
-         
-         player.rewind_length = 0;
-         player.rewind_total_length = 0.0f;
-         player.rewind_incrementer = 10000;
-         player_save_frame();
-
-         audio_lock();
-         audio_play_oneshot( &audio_gate_pass, 1.0f );
-         audio_unlock();
-         break;
-      }
-   }
-   
-   rb_update_transform( &phys->rb );
-}
+#endif
 
 VG_STATIC void player_freecam(void)
 {
@@ -1577,7 +82,6 @@ VG_STATIC int kill_player( int argc, char const *argv[] )
 
 VG_STATIC int reset_player( int argc, char const *argv[] )
 {
-   struct player_phys *phys = &player.phys;
    struct respawn_point *rp = NULL, *r;
 
    if( argc == 1 )
@@ -1603,7 +107,7 @@ VG_STATIC int reset_player( int argc, char const *argv[] )
       for( int i=0; i<world.spawn_count; i++ )
       {
          r = &world.spawns[i];
-         float d = v3_dist2( r->co, phys->rb.co );
+         float d = v3_dist2( r->co, player.co );
          
          vg_info( "Dist %s : %f\n", r->name, d );
          if( d < min_dist )
@@ -1617,12 +121,9 @@ 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] );
+      vg_info( "Player position: %f %f %f\n", player.co[0],
+                                              player.co[1],
+                                              player.co[2] );
 
       if( !world.spawn_count )
          return 0;
@@ -1644,23 +145,8 @@ VG_STATIC int reset_player( int argc, char const *argv[] )
       player.angles[1] = -asinf( delta[1] );
    }
 
-   v4_copy( rp->q, phys->rb.q );
-   v3_copy( rp->co, phys->rb.co );
-   v3_zero( phys->rb.v );
-   
-   phys->vswitch = 1.0f;
-   phys->slip_last = 0.0f;
-   phys->in_air = 1;
-   phys->on_board = 0;
-   m3x3_identity( phys->vr );
-
-   player.mdl.shoes[0] = 1;
-   player.mdl.shoes[1] = 1;
-   
-   rb_update_transform( &phys->rb );
-   
-   v3_add( phys->rb.up, phys->rb.co, phys->cog );
-   v3_zero( phys->cog_v );
+   player.controller = k_player_controller_walk;
+   /* TODO: trigger controller slurp */
 
    player_save_frame();
    return 1;
@@ -1673,49 +159,9 @@ VG_STATIC void reset_player_poll( int argc, char const *argv[] )
       for( int i=0; i<world.spawn_count; i++ )
       {
          struct respawn_point *r = &world.spawns[i];
-
          console_suggest_score_text( r->name, argv[argc-1], 0 );
       }
    }
 }
 
-VG_STATIC void player_physics_gui(void)
-{
-   return;
-
-       vg_uictx.cursor[0] = 0;
-       vg_uictx.cursor[1] = vg.window_y - 128;
-       vg_uictx.cursor[3] = 14;
-       ui_fill_x();
-
-   char buf[128];
-
-   snprintf( buf, 127, "v: %6.3f %6.3f %6.3f\n", player.phys.rb.v[0],
-                                                 player.phys.rb.v[1],
-                                                 player.phys.rb.v[2] );
-
-   ui_text( vg_uictx.cursor, buf, 1, 0 );
-       vg_uictx.cursor[1] += 14;
-
-
-   snprintf( buf, 127, "a: %6.3f %6.3f %6.3f (%6.3f)\n", player.phys.a[0],
-                                                     player.phys.a[1],
-                                                     player.phys.a[2],
-                                                     v3_length(player.phys.a));
-   ui_text( vg_uictx.cursor, buf, 1, 0 );
-       vg_uictx.cursor[1] += 14;
-
-   float normal_acceleration = v3_dot( player.phys.a, player.phys.rb.up );
-   snprintf( buf, 127, "Normal acceleration: %6.3f\n", normal_acceleration );
-
-   ui_text( vg_uictx.cursor, buf, 1, 0 );
-       vg_uictx.cursor[1] += 14;
-
-   snprintf( buf, 127, "Normal Pressure: %6.3f\n", player.normal_pressure );
-   ui_text( vg_uictx.cursor, buf, 1, 0 );
-       vg_uictx.cursor[1] += 14;
-
-
-}
-
 #endif /* PLAYER_PHYSICS_H */
diff --git a/player_physics_skate.h b/player_physics_skate.h
new file mode 100644 (file)
index 0000000..f4b960f
--- /dev/null
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef PLAYER_PHYSICS_SKATE_H
+#define PLAYER_PHYSICS_SKATE_H
+
+#include "player.h"
+#include "camera.h"
+
+VG_STATIC struct player_skate
+{
+   struct skate_phys
+   {
+      enum skate_activity
+      {
+         k_skate_activity_air,
+         k_skate_activity_ground,
+         k_skate_activity_grind
+      }
+      activity,
+      activity_prev;
+
+      v3f v_prev, a, m, bob, vl; /* TODO: please clarify */
+
+      float iY, siY;   /* Yaw inertia */
+
+      float vswitch, slip, slip_last, reverse;
+      float grab, jump;
+      v2f grab_mouse_delta;
+
+      v3f throw_v;
+      v3f cog, 
+          cog_v;
+
+      int lift_frames;
+
+      double start_push, 
+             cur_push;
+
+      int charging_jump, jump_dir;
+
+      m3x3f vr,vr_pstep;
+   }
+   phys,
+   phys_frame;
+
+   float normal_pressure;
+   v3f debug_mmcollect_lat,
+       debug_mmcollect_vert;
+
+   struct land_prediction
+   {
+      v3f   log[50];
+      v3f   n;
+      u32   log_length;
+      float score;
+
+      enum prediction_type
+      {
+         k_prediction_none,
+         k_prediction_land,
+         k_prediction_grind
+      }
+      type;
+
+      u32   colour;
+   }
+   predictions[22];
+   u32 prediction_count;
+
+   rigidbody rbf, rbb;
+}
+player_skater;
+
+/*
+ * Gets the closest grindable edge to the player within max_dist 
+ */
+VG_STATIC struct grind_edge *player_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;
+}
+
+/* 
+ * Trace a path given a velocity rotation.
+ *
+ * TODO: this MIGHT be worth doing RK4 on the gravity field.
+ */
+VG_STATIC void player_score_vr_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;
+
+   prediction->log_length = 0;
+
+   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 );
+      
+      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_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 ++ ] ); 
+         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;
+   }
+}
+
+/*
+ * Calculate best launch trajectory
+ */
+VG_STATIC void player_approximate_best_trajectory( struct player_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->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_prediction *p = &s->predictions[ s->prediction_count ++ ];
+
+      float vmod = ((float)m / 15.0f)*0.09f;
+
+      m3x3f vr;
+      v4f vr_q;
+
+      q_axis_angle( vr_q, axis, vmod );
+      q_m3x3( vr_q, vr );
+
+      player_score_vr_path( player.rb.co, player.rb.v, vr, p );
+
+      if( p->type != k_prediction_none )
+      {
+         if( p->score < min_score )
+         {
+            min_score = p->score;
+            best_vmod = vmod;
+         }
+
+         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->phys.vr );
+
+   q_axis_angle( vr_q, axis, best_vmod );
+   q_m3x3( vr_q, s->phys.vr_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;
+   }
+}
+
+VG_STATIC void player_skate_apply_grab_model( struct player_skate *s )
+{
+   float grabt = player.input_grab->axis.value;
+
+   if( grabt > 0.5f )
+   {
+      v2_muladds( s->phys.grab_mouse_delta, vg.mouse_delta, 0.02f, 
+                  s->phys.grab_mouse_delta );
+
+      v2_normalize_clamp( s->phys.grab_mouse_delta );
+   }
+   else
+      v2_zero( s->phys.grab_mouse_delta );
+
+   s->phys.grab = vg_lerpf( s->phys.grab, grabt, 8.4f * VG_TIMESTEP_FIXED );
+}
+
+/*
+ * Computes friction and surface interface model
+ */
+VG_STATIC void player_skate_apply_friction_model( struct player_skate *s )
+{
+   if( s->phys.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->phys.slip = slip;
+   s->phys.reverse = -vg_signf(vel[2]);
+
+   float substep = VG_TIMESTEP_FIXED;
+   float fwd_resistance = k_friction_resistance;
+
+   vel[2] = stable_force( vel[2],vg_signf(vel[2]) * -fwd_resistance*substep);
+   vel[0] = stable_force( vel[0],vg_signf(vel[0]) * -k_friction_lat*substep);
+
+   /* Pushing additive force */
+
+   if( !player.input_jump->button.value )
+   {
+      if( player.input_push->button.value )
+      {
+         if( (vg.time - s->phys.cur_push) > 0.25 )
+         {
+            s->phys.start_push = vg.time;
+         }
+
+         s->phys.cur_push = vg.time;
+
+         double push_time = vg.time - s->phys.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->phys.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->phys.jump+grab)*0.4f),
+         steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
+
+   s->phys.iY -= steer_scaled * VG_TIMESTEP_FIXED;
+}
+
+VG_STATIC void player_skate_apply_jump_model( struct player_skate *s )
+{
+   int charging_jump_prev = s->phys.charging_jump;
+   s->phys.charging_jump = player.input_jump->button.value;
+
+   /* Cannot charge this in air */
+   if( s->phys.activity != k_skate_activity_ground )
+      s->phys.charging_jump = 0;
+
+   if( s->phys.charging_jump )
+   {
+      s->phys.jump += VG_TIMESTEP_FIXED * k_jump_charge_speed;
+
+      if( !charging_jump_prev )
+         s->phys.jump_dir = s->phys.reverse > 0.0f? 1: 0;
+   }
+   else
+   {
+      s->phys.jump -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
+   }
+
+   s->phys.jump = vg_clampf( s->phys.jump, 0.0f, 1.0f );
+
+   if( s->phys.activity == k_skate_activity_air )
+      return;
+
+   /* player let go after charging past 0.2: trigger jump */
+   if( !s->phys.charging_jump && s->phys.jump > 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->phys.jump;
+      v3_muladds( player.rb.v, jumpdir, force, player.rb.v );
+      s->phys.jump = 0.0f;
+
+      player.jump_time = vg.time;
+      
+      /* TODO: Move to audio file */
+      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();
+   }
+}
+
+VG_STATIC void player_skate_apply_grind_model( struct player_skate *s,
+                                               rb_ct *manifold, int len )
+{
+   if( len == 0 )
+   {
+      if( s->phys.activity == k_skate_activity_grind )
+      {
+         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();
+
+         s->phys.activity = k_skate_activity_air;
+      }
+      return;
+   }
+
+   v2f steer = { player.input_js1h->axis.value,
+                 player.input_js1v->axis.value };
+
+   float l2 = v2_length2( steer );
+   if( l2 > 1.0f )
+      v2_muls( steer, 1.0f/sqrtf(l2), steer );
+
+   s->phys.iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
+
+   float iX = steer[1] * s->phys.reverse 
+                  * k_steer_air * VG_TIMESTEP_FIXED;
+
+   static float siX = 0.0f;
+   siX = vg_lerpf( siX, iX, k_steer_air_lerp );
+   
+   v4f rotate;
+   q_axis_angle( rotate, player.rb.to_world[0], siX );
+   q_mul( rotate, player.rb.q, player.rb.q );
+
+   s->phys.slip = 0.0f;
+   s->phys.activity = k_skate_activity_grind;
+
+   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, 
+            VG_TIMESTEP_FIXED * 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->phys.vr );
+   m3x3_identity( s->phys.vr_pstep );
+
+   if( s->phys.activity_prev != k_skate_activity_grind )
+   {
+      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();
+   }
+}
+
+/*
+ * Air control, no real physics
+ */
+VG_STATIC void player_skate_apply_air_model( struct player_skate *s )
+{
+   if( s->phys.activity != k_skate_activity_air )
+      return;
+
+   if( s->phys.activity_prev != k_skate_activity_air )
+      player_approximate_best_trajectory( s );
+
+   m3x3_mulv( s->phys.vr, 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->phys.vr, 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 = player_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)*3.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 };
+
+   float l2 = v2_length2( steer );
+   if( l2 > 1.0f )
+      v2_muls( steer, 1.0f/sqrtf(l2), steer );
+
+   s->phys.iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
+
+   float iX = steer[1] * 
+              s->phys.reverse * k_steer_air 
+              * limiter * VG_TIMESTEP_FIXED;
+
+   static float siX = 0.0f;
+   siX = vg_lerpf( siX, iX, k_steer_air_lerp );
+   
+   v4f rotate;
+   q_axis_angle( rotate, player.rb.to_world[0], siX );
+   q_mul( rotate, player.rb.q, player.rb.q );
+   
+#if 0
+   v2f target = {0.0f,0.0f};
+   v2_muladds( target, (v2f){ vg_get_axis("grabh"), vg_get_axis("grabv") },
+               player_skate.phys.grab, target );
+#endif
+}
+
+VG_STATIC void player_regular_collider_configuration( struct player_skate *s )
+{
+   /* Standard ground configuration */
+   m3x3_copy( player.rb.to_world, s->rbf.to_world );
+   m3x3_copy( player.rb.to_world, s->rbb.to_world );
+
+   v3f front = {0.0f,0.0f,-k_board_length},
+       back  = {0.0f,0.0f, k_board_length};
+
+   m4x3_mulv( player.rb.to_world, front, s->rbf.co );
+   m4x3_mulv( player.rb.to_world, back,  s->rbb.co );
+   v3_copy( s->rbf.co, s->rbf.to_world[3] );
+   v3_copy( s->rbb.co, s->rbb.to_world[3] );
+
+   m4x3_invert_affine( s->rbf.to_world, s->rbf.to_local );
+   m4x3_invert_affine( s->rbb.to_world, s->rbb.to_local );
+
+   rb_update_bounds( &s->rbf );
+   rb_update_bounds( &s->rbb );
+}
+
+VG_STATIC void player_grind( struct player_skate *s )
+{
+   v3f closest;
+   int idx = bh_closest_point( world.grind_bh, player.rb.co, 
+                               closest, INFINITY );
+   if( idx == -1 )
+      return;
+
+   struct grind_edge *edge = &world.grind_edges[ idx ];
+
+   vg_line( player.rb.co, closest, 0xff000000 );
+   vg_line_cross( closest, 0xff000000, 0.3f );
+   vg_line( edge->p0, edge->p1, 0xff000000 );
+
+   v3f grind_delta;
+   v3_sub( closest, player.rb.co, grind_delta );
+   
+   float p = v3_dot( player.rb.to_world[2], grind_delta );
+   v3_muladds( grind_delta, player.rb.to_world[2], -p, grind_delta );
+   
+   float a = vg_maxf( 0.0f, 4.0f-v3_dist2( closest, player.rb.co ) );
+   v3_muladds( player.rb.v, grind_delta, a*0.2f, player.rb.v );
+}
+
+VG_STATIC int player_update_grind_collision( struct player_skate *s, 
+                                             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 = player_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;
+}
+
+/* 
+ * Handles connection between the player and the ground
+ */
+VG_STATIC void player_skate_apply_interface_model( struct player_skate *s,
+                                                   rb_ct *manifold, int len )
+{
+   if( !((s->phys.activity == k_skate_activity_ground) ||
+         (s->phys.activity == k_skate_activity_air )) )
+      return;
+
+   v3f surface_avg;
+   v3_zero( surface_avg );
+
+   /*
+    *
+    * EXPERIMENTAL
+    * ================================================================
+    */
+   if( s->phys.activity == k_skate_activity_air )
+      s->normal_pressure = 0.0f;
+   else
+      s->normal_pressure = v3_dot( player.rb.to_world[1], player.rb.v );
+
+   v3f p0_0, p0_1,
+       p1_0, p1_1,
+       n0, n1;
+
+   float t0, t1;
+
+   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;
+
+   v3_copy( s->rbf.co, p0_0 );
+   v3_copy( s->rbb.co,  p1_0 );
+
+   v3_muladds( p0_0, player.rb.to_world[1], -disp_k, p0_1 );
+   v3_muladds( p1_0, player.rb.to_world[1], -disp_k, p1_1 );
+
+   int cast0 = spherecast_world( p0_0, p0_1, 0.2f, &t0, n0 ),
+       cast1 = spherecast_world( p1_0, p1_1, 0.2f, &t1, n1 );
+
+   v3f animp0, animp1;
+
+   m4x3f temp;
+   m3x3_copy( player.rb.to_world, temp );
+   if( cast0 != -1 )
+   {
+      v3_lerp( p0_0, p0_1, t0, temp[3] );
+      v3_copy( temp[3], animp0 );
+      debug_sphere( temp, 0.2f, VG__PINK );
+      
+      v3f F, delta;
+      v3_sub( p0_0, player.rb.co, delta );
+      
+      float displacement = vg_clampf( 1.0f-t0, 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 );
+   }
+   else
+      v3_copy( p0_1, animp0 );
+
+   if( cast1 != -1 )
+   {
+      v3_lerp( p1_0, p1_1, t1, temp[3] );
+      v3_copy( temp[3], animp1 );
+      debug_sphere( temp, 0.2f, VG__PINK );
+
+      v3f F, delta;
+      v3_sub( p1_0, player.rb.co, delta );
+      
+      float displacement = vg_clampf( 1.0f-t1, 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 );
+   }
+   else
+      v3_copy( p1_1, animp1 );
+
+   v3f animavg, animdelta;
+   v3_add( animp0, animp1, animavg );
+   v3_muls( animavg, 0.5f, animavg );
+
+   v3_sub( animp1, animp0, animdelta );
+   v3_normalize( animdelta );
+
+   m4x3_mulv( player.rb.to_local, animavg, player.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( player.board_rotation, (v3f){ 1.0f, 0.0f, 0.0f }, angle );
+
+   /*
+    * ================================================================
+    * EXPERIMENTAL
+    */
+
+   if( len == 0 && !((cast0 !=-1)&&(cast1!=-1)) )
+   {
+      s->phys.lift_frames ++;
+
+      if( s->phys.lift_frames >= 8 )
+         s->phys.activity = k_skate_activity_air;
+   }
+   else
+   {
+      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->phys.lift_frames ++;
+
+         if( s->phys.lift_frames >= 8 )
+            s->phys.activity = k_skate_activity_air;
+      }
+      else
+      {
+         s->phys.activity = k_skate_activity_ground;
+         s->phys.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 0
+         v3f p0, p1;
+         v3_add( phys->rb.co, projected, p0 );
+         v3_add( phys->rb.co, player_skate.phys.up, p1 );
+         vg_line( phys->rb.co, p0, 0xff00ff00 );
+         vg_line( phys->rb.co, p1, 0xff000fff );
+#endif
+
+         if( fabsf(angle) < 0.999f )
+         {
+            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 player_collision_response( struct player_skate *s,
+                                          rb_ct *manifold, int len )
+{
+   /* TODO: RElocate */
+   /* Throw / collect routine 
+    *
+    * TODO: Max speed boost
+    */
+   if( player.input_grab->axis.value > 0.5f )
+   {
+      if( s->phys.activity == k_skate_activity_ground )
+      {
+         /* Throw */
+         v3_muls( player.rb.to_world[1], k_mmthrow_scale, s->phys.throw_v );
+      }
+   }
+   else
+   {
+      /* Collect */
+      float doty = v3_dot( player.rb.to_world[1], s->phys.throw_v );
+      
+      v3f Fl, Fv;
+      v3_muladds( s->phys.throw_v, player.rb.to_world[1], -doty, Fl);
+
+      if( s->phys.activity == k_skate_activity_ground )
+      {
+         v3_muladds( player.rb.v,    Fl,  k_mmcollect_lat, player.rb.v );
+         v3_muladds( s->phys.throw_v, Fl, -k_mmcollect_lat, s->phys.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->phys.throw_v, Fv, k_mmcollect_vert, s->phys.throw_v );
+
+      v3_copy( Fl, s->debug_mmcollect_lat );
+      v3_copy( Fv, s->debug_mmcollect_vert );
+   }
+
+   /* Decay */
+   if( v3_length2( s->phys.throw_v ) > 0.0001f )
+   {
+      v3f dir;
+      v3_copy( s->phys.throw_v, dir );
+      v3_normalize( dir );
+
+      float max = v3_dot( dir, s->phys.throw_v ),
+            amt = vg_minf( k_mmdecay * k_rb_delta, max );
+
+      v3_muladds( s->phys.throw_v, dir, -amt, s->phys.throw_v );
+   }
+
+
+   /* TODO: RElocate */
+   {
+
+   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->phys.cog, ideal_diff );
+
+   /* Apply velocities */
+   v3f rv;
+   v3_sub( player.rb.v, s->phys.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;
+
+   v3_muladds( s->phys.cog_v, F, -rb, s->phys.cog_v );
+   }
+
+   /* stripped down presolve */
+   for( int i=0; i<len; i++ )
+   {
+      rb_ct *ct = &manifold[i];
+      ct->bias = -0.2f * k_rb_rate * vg_minf( 0.0f, -ct->p+k_penetration_slop );
+
+      rb_debug_contact( ct );
+   }
+
+   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 )
+         {
+            player_kill();
+            return;
+         }
+
+         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 );
+      }
+   }
+   
+   /* early integrate this */
+   s->phys.cog_v[1] += -9.8f * k_rb_delta;
+   v3_muladds( s->phys.cog, s->phys.cog_v, k_rb_delta, s->phys.cog );
+}
+
+VG_STATIC void player_skate_update( struct player_skate *s )
+{
+   s->phys.activity_prev = s->phys.activity;
+
+   rb_ct manifold[72],
+         *interface_manifold = NULL,
+         *grind_manifold = NULL;
+   
+   player_regular_collider_configuration( s );
+
+   int nfront = player_collide_sphere( &s->rbf, manifold ),
+       nback  = player_collide_sphere( &s->rbb, manifold + nfront ),
+       interface_len = nfront + nback;
+
+   interface_manifold = manifold;
+   grind_manifold = manifold + interface_len;
+
+   int grind_len = player_update_grind_collision( s, grind_manifold );
+
+   player_skate_apply_grind_model( s, grind_manifold, grind_len );
+   player_skate_apply_interface_model( s, manifold, interface_len );
+
+   rb_presolve_contacts( manifold, interface_len + grind_len );
+   player_collision_response( s, manifold, interface_len + grind_len );
+
+   player_skate_apply_grab_model( s );
+   player_skate_apply_friction_model( s );
+   player_skate_apply_jump_model( s );
+   player_skate_apply_air_model( s );
+
+   v3f gravity = { 0.0f, -9.6f, 0.0f };
+   v3_muladds( player.rb.v, gravity, k_rb_delta, player.rb.v );
+
+   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 );
+
+   player.rb.v[1] += -9.6f * VG_TIMESTEP_FIXED;
+
+   v3_muladds( player.rb.co, player.rb.v, VG_TIMESTEP_FIXED, player.rb.co );
+}
+
+VG_STATIC void player_physics_gui(void)
+{
+#if 0
+   return;
+
+       vg_uictx.cursor[0] = 0;
+       vg_uictx.cursor[1] = vg.window_y - 128;
+       vg_uictx.cursor[3] = 14;
+       ui_fill_x();
+
+   char buf[128];
+
+   snprintf( buf, 127, "v: %6.3f %6.3f %6.3f\n", player.phys.rb.v[0],
+                                                 player.phys.rb.v[1],
+                                                 player.phys.rb.v[2] );
+
+   ui_text( vg_uictx.cursor, buf, 1, 0 );
+       vg_uictx.cursor[1] += 14;
+
+
+   snprintf( buf, 127, "a: %6.3f %6.3f %6.3f (%6.3f)\n", player.phys.a[0],
+                                                     player.phys.a[1],
+                                                     player.phys.a[2],
+                                                     v3_length(player.phys.a));
+   ui_text( vg_uictx.cursor, buf, 1, 0 );
+       vg_uictx.cursor[1] += 14;
+
+   float normal_acceleration = v3_dot( player.phys.a, player.phys.rb.up );
+   snprintf( buf, 127, "Normal acceleration: %6.3f\n", normal_acceleration );
+
+   ui_text( vg_uictx.cursor, buf, 1, 0 );
+       vg_uictx.cursor[1] += 14;
+
+   snprintf( buf, 127, "Normal Pressure: %6.3f\n", player.normal_pressure );
+   ui_text( vg_uictx.cursor, buf, 1, 0 );
+       vg_uictx.cursor[1] += 14;
+#endif
+}
+
+#endif /* PLAYER_PHYSICS_SKATE_H */
diff --git a/player_physics_walk.h b/player_physics_walk.h
new file mode 100644 (file)
index 0000000..35dbf29
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#define PLAYER_PHYSICS_WALK_H
+#ifndef PLAYER_PHYSICS_WALK_H
+#define PLAYER_PHYSICS_WALK_H
+
+#include "player.h"
+#include "camera.h"
+
+VG_STATIC struct player_walk
+{
+   struct
+   {
+      enum walk_activityaaaaaa
+      {
+         k_walk_activity_airaaaa,
+         k_walk_activity_groundaaaaaa
+      }
+      activity;
+   }
+   state;
+
+   rigidbody capsule;      /* TODO: rigidbody::collider_capsule */
+}
+player_walky = 
+{
+   .capsule = { .inf.capsule.height = 2.0f, .inf.capsule.radius = 0.3f }
+};
+
+VG_STATIC void player_walk_collider_configuration(void)
+{
+   v3_add( player.rb.co, (v3f){0.0f,1.0f,0.0f}, player_walky.capsule.co );
+   rb_update_transform( &player_walky.capsule );
+
+#if 0
+   float h0 = 0.3f,
+         h1 = 0.9f;
+
+   rigidbody *rbf = &player.collide_front,
+             *rbb = &player.collide_back;
+
+   v3_add( phys->rb.co, (v3f){0.0f,h0,0.0f}, rbf->co );
+   v3_add( phys->rb.co, (v3f){0.0f,h1,0.0f}, rbb->co );
+   v3_copy( rbf->co, rbf->to_world[3] );
+   v3_copy( rbb->co, rbb->to_world[3] );
+   m4x3_invert_affine( rbf->to_world, rbf->to_local );
+   m4x3_invert_affine( rbb->to_world, rbb->to_local );
+
+   rb_update_bounds( rbf );
+   rb_update_bounds( rbb );
+#endif
+}
+
+__attribute__ ((deprecated))
+VG_STATIC int player_walk_surface_standable( v3f n )
+{
+   return v3_dot( n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f;
+}
+
+__attribute__ ((deprecated))
+VG_STATIC void player_walk_stepdown( struct player_walk *w )
+{
+   float max_dist = 0.4f;
+
+   v3f pa, pb;
+   v3_copy( player.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 ) )
+      {
+         w->state.activity = k_walk_activity_ground;
+         v3_lerp( pa, pb, t+0.001f, player.rb.co );
+         player.rb.co[1] -= 0.3f;
+      }
+   }
+}
+
+__attribute__ ((deprecated))
+VG_STATIC void player_walk_physics( struct player_walk *w )
+{
+   v3_zero( player.rb.w );
+   q_axis_angle( player.rb.q, (v3f){0.0f,1.0f,0.0f}, -player.angles[0] );
+
+   rb_ct manifold[64];
+   int len;
+
+   v3f forward_dir = { sinf(player.angles[0]),0.0f,-cosf(player.angles[0]) };
+   v3f right_dir = { -forward_dir[2], 0.0f, forward_dir[0] };
+
+   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 );
+
+   if( w->state.activity == k_walk_activity_air )
+   {
+      player_walk_collider_configuration();
+
+      /* allow player to accelerate a bit */
+      v3f walk_3d;
+      v3_muls( forward_dir, walk[1], walk_3d );
+      v3_muladds( walk_3d, right_dir, walk[0], walk_3d );
+
+      float current_vel = fabsf(v3_dot( walk_3d, player.rb.v )),
+            new_vel     = current_vel + VG_TIMESTEP_FIXED*999999.0f,
+            clamped_new = vg_clampf( new_vel, 0.0f, k_walkspeed ),
+            vel_diff    = vg_maxf( 0.0f, clamped_new - current_vel );
+
+      v3_muladds( player.rb.v, right_dir,   walk[0] * vel_diff, player.rb.v );
+      v3_muladds( player.rb.v, forward_dir, walk[1] * vel_diff, player.rb.v );
+
+      len = rb_capsule_scene( &w->capsule, &world.rb_geo, manifold );
+      rb_presolve_contacts( manifold, len );
+
+      for( int i=0; i<len; i++ )
+      {
+         struct contact *ct = &manifold[i];
+         if( v3_dot( ct->n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f )
+            w->state.activity = k_walk_activity_ground;
+      }
+      
+      /*
+       * TODO: Compression
+       */
+      for( int j=0; j<5; j++ )
+      {
+         for( int i=0; i<len; i++ )
+         {
+            struct contact *ct = &manifold[i];
+            
+            /*normal */
+            float vn = -v3_dot( player.rb.v, 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 );
+
+            v3_add( impulse, player.rb.v, player.rb.v );
+
+            /* friction */
+            for( int j=0; j<2; j++ )
+            {
+               float     f = k_friction * ct->norm_impulse,
+                        vt = v3_dot( player.rb.v, ct->t[j] ),
+                    lambda = -vt;
+               
+               float temp = ct->tangent_impulse[j];
+               ct->tangent_impulse[j] = vg_clampf( temp + lambda, -f, f );
+               lambda = ct->tangent_impulse[j] - temp;
+
+               v3_muladds( player.rb.v, ct->t[j], lambda, player.rb.v );
+            }
+         }
+      }
+
+      player.rb.v[1] += -k_gravity * k_rb_delta;
+      v3_muladds( player.rb.co, player.rb.v, k_rb_delta, player.rb.co );
+   }
+   else
+   {
+      player.walk = v2_length( walk );
+
+      if( player.input_walk->button.value )
+         v2_muls( walk, 0.5f, walk );
+
+      v2_muls( walk, k_walkspeed * VG_TIMESTEP_FIXED, walk );
+
+      /* Do XY translation */
+      v3f walk_apply, walk_measured;
+      v3_zero( walk_apply );
+      v3_muladds( walk_apply, right_dir,   walk[0], walk_apply );
+      v3_muladds( walk_apply, forward_dir, walk[1], walk_apply );
+      v3_add( walk_apply, player.rb.co, player.rb.co );
+
+      /* Directly resolve collisions */
+      player_walk_collider_configuration();
+      len = rb_capsule_scene( &w->capsule, &world.rb_geo, manifold );
+
+      v3f dt;
+      v3_zero( dt );
+      for( int j=0; j<8; j++ )
+      {
+         for( int i=0; i<len; i++ )
+         {
+            struct contact *ct = &manifold[i];
+
+            float resolved_amt = v3_dot( ct->n, dt ),
+                  remaining    = (ct->p-k_penetration_slop) - resolved_amt,
+                  apply        = vg_maxf( remaining, 0.0f ) * 0.3f;
+
+            v3_muladds( dt, ct->n, apply, dt );
+         }
+      }
+      v3_add( dt, player.rb.co, player.rb.co );
+
+      v3_add( dt, walk_apply, walk_measured );
+      v3_divs( walk_measured, VG_TIMESTEP_FIXED, player.rb.v );
+
+      if( len )
+      {
+         struct world_material *surface_mat = world_contact_material(manifold);
+         player.surface_prop = surface_mat->info.surface_prop;
+      }
+
+      w->state.activity = k_walk_activity_air;
+
+      /* jump */
+      if( player.input_jump->button.value )
+      {
+         player.rb.v[1] = 5.0f;
+         return;
+      }
+
+      /* Check if grounded by current manifold */
+      for( int i=0; i<len; i++ )
+      {
+         struct contact *ct = &manifold[i];
+         if( player_walk_surface_standable( ct->n ) )
+            w->state.activity = k_walk_activity_ground;
+      }
+      
+      /* otherwise... */
+      if( w->state.activity == k_walk_activity_air )
+         player_walk_stepdown( w );
+   }
+}
+
+__attribute__ ((deprecated))
+VG_STATIC void player_walk_animate( void *_w, 
+                                    struct skeleton *sk, player_pose pose )
+{
+   struct player_walk *w = _w;
+
+   {
+      float fly = (w->state.activity == k_walk_activity_air)? 1.0f: 0.0f,
+            rate;
+
+      if( w->state.activity == k_walk_activity_air )
+         rate = 2.4f;
+      else
+         rate = 9.0f;
+
+      player.ffly  = vg_lerpf( player.ffly, fly, rate*vg.time_delta );
+      player.frun  = vg_lerpf( player.frun,
+                               player.walk *
+                               (1.0f + player.input_walk->button.value*0.5f),
+                               2.0f*vg.time_delta );
+   }
+
+   player_pose apose, bpose;
+
+   if( player.walk > 0.025f )
+   {
+      /* TODO move */
+      float walk_norm = 30.0f/(float)player.mdl.anim_walk->length,
+            run_norm  = 30.0f/(float)player.mdl.anim_run->length,
+            walk_adv  = vg_lerpf( walk_norm, run_norm, player.walk );
+
+      player.walk_timer += walk_adv * vg.time_delta;
+   }
+   else
+   {
+      player.walk_timer = 0.0f;
+   }
+
+   float walk_norm = (float)player.mdl.anim_walk->length/30.0f,
+         run_norm  = (float)player.mdl.anim_run->length/30.0f,
+         t = player.walk_timer,
+         l = vg_clampf( player.frun*15.0f, 0.0f, 1.0f ),
+         idle_walk = vg_clampf( (player.frun-0.1f)/(1.0f-0.1f), 0.0f, 1.0f );
+
+   /* walk/run */
+   skeleton_sample_anim( sk, player.mdl.anim_walk, t*walk_norm, apose );
+   skeleton_sample_anim( sk, player.mdl.anim_run,  t*run_norm, bpose );
+
+   skeleton_lerp_pose( sk, apose, bpose, l, apose );
+
+   /* idle */
+   skeleton_sample_anim( sk, player.mdl.anim_idle, vg.time*0.1f, bpose );
+   skeleton_lerp_pose( sk, apose, bpose, 1.0f-idle_walk, apose );
+
+   /* air */
+   skeleton_sample_anim( sk, player.mdl.anim_jump, vg.time*0.6f, bpose );
+   skeleton_lerp_pose( sk, apose, bpose, player.ffly, apose );
+
+   skeleton_apply_pose( &player.mdl.sk, apose, k_anim_apply_defer_ik );
+   skeleton_apply_ik_pass( &player.mdl.sk );
+   skeleton_apply_pose( &player.mdl.sk, apose, k_anim_apply_deffered_only );
+
+   v3_copy( player.mdl.sk.final_mtx[player.mdl.id_head-1][3], 
+               player.mdl.cam_pos );
+
+
+
+   skeleton_apply_inverses( &player.mdl.sk );
+
+   m4x3f mtx;
+   v4f rot;
+   q_axis_angle( rot, (v3f){0.0f,1.0f,0.0f}, -player.angles[0] - VG_PIf*0.5f );
+   q_m3x3( rot, mtx );
+   v3_copy( player.visual_transform[3], mtx[3] );
+
+   skeleton_apply_transform( &player.mdl.sk, mtx );
+   skeleton_debug( &player.mdl.sk );
+}
+
+#endif /* PLAYER_PHYSICS_WALK_H */
index d39cf1b6012926941f63860c8e441927f160bc4b..5c497f21ada170c53a4d7850095b54beffcd025f 100644 (file)
@@ -16,6 +16,8 @@
 VG_STATIC void rb_tangent_basis( v3f n, v3f tx, v3f ty );
 VG_STATIC bh_system bh_system_rigidbodies;
 
+
+
 #ifndef RIGIDBODY_H
 #define RIGIDBODY_H
 
@@ -33,7 +35,8 @@ VG_STATIC const float
    k_damp_angular     = 0.1f,                /* scale angular  1/(1+x) */
    k_penetration_slop = 0.01f,
    k_inertia_scale    = 8.0f,
-   k_phys_baumgarte   = 0.2f;
+   k_phys_baumgarte   = 0.2f,
+   k_gravity          = 9.6f;
 
 VG_STATIC float
    k_limit_bias       = 0.02f,
@@ -72,6 +75,24 @@ VG_STATIC void rb_register_cvar(void)
 
 typedef struct rigidbody rigidbody;
 typedef struct contact rb_ct;
+typedef struct rb_sphere rb_sphere;
+typedef struct rb_capsule rb_capsule;
+typedef struct rb_scene rb_scene;
+
+struct rb_sphere
+{
+   float radius;
+};
+
+struct rb_capsule
+{
+   float height, radius;
+};
+
+struct rb_scene
+{
+   bh_tree *bh_scene;
+};
 
 struct rigidbody
 {
@@ -89,23 +110,9 @@ struct rigidbody
    
    union
    {
-      struct rb_sphere
-      {
-         float radius;
-      }
-      sphere;
-
-      struct rb_capsule
-      {
-         float height, radius;
-      } 
-      capsule;
-
-      struct rb_scene
-      {
-         bh_tree *bh_scene;
-      }
-      scene;
+      struct rb_sphere sphere;
+      struct rb_capsule capsule;
+      struct rb_scene scene;
    }
    inf;
 
@@ -857,6 +864,7 @@ VG_STATIC void rb_capsule_manifold_init( capsule_manifold *manifold )
    manifold->t1 = -INFINITY;
 }
 
+__attribute__ ((deprecated))
 VG_STATIC int rb_capsule_manifold_done( rigidbody *rba, rigidbody *rbb, 
                                      capsule_manifold *manifold, rb_ct *buf )
 {
@@ -918,6 +926,60 @@ VG_STATIC int rb_capsule_manifold_done( rigidbody *rba, rigidbody *rbb,
    return count;
 }
 
+VG_STATIC int rb_capsule__manifold_done( m4x3f mtx, rb_capsule *c,
+                                         capsule_manifold *manifold,
+                                         rb_ct *buf )
+{
+   v3f p0, p1;
+   v3_muladds( mtx[3], mtx[1], -c->height*0.5f+c->radius, p0 );
+   v3_muladds( mtx[3], mtx[1],  c->height*0.5f-c->radius, p1 );
+
+   int count = 0;
+   if( manifold->t0 <= 1.0f )
+   {
+      rb_ct *ct = buf;
+
+      v3f pa;
+      v3_muls( p0, 1.0f-manifold->t0, pa );
+      v3_muladds( pa, p1, manifold->t0, pa );
+
+      float d = v3_length( manifold->d0 );
+      v3_muls( manifold->d0, 1.0f/d, ct->n );
+      v3_muladds( pa, ct->n, -c->radius, ct->co );
+
+      ct->p = manifold->r0 - d;
+      ct->type = k_contact_type_default;
+      count ++;
+   }
+
+   if( (manifold->t1 >= 0.0f) && (manifold->t0 != manifold->t1) )
+   {
+      rb_ct *ct = buf+count;
+
+      v3f pa;
+      v3_muls( p0, 1.0f-manifold->t1, pa );
+      v3_muladds( pa, p1, manifold->t1, pa );
+
+      float d = v3_length( manifold->d1 );
+      v3_muls( manifold->d1, 1.0f/d, ct->n );
+      v3_muladds( pa, ct->n, -c->radius, ct->co );
+
+      ct->p = manifold->r1 - d;
+      ct->type = k_contact_type_default;
+
+      count ++;
+   }
+
+   /*
+    * Debugging
+    */
+
+   if( count == 2 )
+      vg_line( buf[0].co, buf[1].co, 0xff0000ff );
+
+   return count;
+}
+
 VG_STATIC int rb_capsule_sphere( rigidbody *rba, rigidbody *rbb, rb_ct *buf )
 {
    float h = rba->inf.capsule.height,
@@ -1536,9 +1598,63 @@ VG_STATIC int rb_box_scene( rigidbody *rba, rigidbody *rbb, rb_ct *buf )
    return count;
 }
 
+VG_STATIC int rb_capsule__triangle( m4x3f mtxA, rb_capsule *c,
+                                    v3f tri[3], rb_ct *buf )
+{
+   v3f pc, p0w, p1w;
+   v3_muladds( mtxA[3], mtxA[1], -c->height*0.5f+c->radius, p0w );
+   v3_muladds( mtxA[3], mtxA[1],  c->height*0.5f-c->radius, p1w );
+
+   capsule_manifold manifold;
+   rb_capsule_manifold_init( &manifold );
+   
+   v3f c0, c1;
+   closest_on_triangle_1( p0w, tri, c0 );
+   closest_on_triangle_1( p1w, tri, c1 );
+
+   v3f d0, d1, da;
+   v3_sub( c0, p0w, d0 );
+   v3_sub( c1, p1w, d1 );
+   v3_sub( p1w, p0w, da );
+   
+   v3_normalize(d0);
+   v3_normalize(d1);
+   v3_normalize(da);
+
+   if( v3_dot( da, d0 ) <= 0.01f )
+      rb_capsule_manifold( p0w, c0, 0.0f, c->radius, &manifold );
+
+   if( v3_dot( da, d1 ) >= -0.01f )
+      rb_capsule_manifold( p1w, c1, 1.0f, c->radius, &manifold );
+
+   for( int i=0; i<3; i++ )
+   {
+      int i0 = i,
+          i1 = (i+1)%3;
+
+      v3f ca, cb;
+      float ta, tb;
+      closest_segment_segment( p0w, p1w, tri[i0], tri[i1], &ta, &tb, ca, cb );
+      rb_capsule_manifold( ca, cb, ta, c->radius, &manifold );
+   }
+
+   v3f v0, v1, n;
+   v3_sub( tri[1], tri[0], v0 );
+   v3_sub( tri[2], tri[0], v1 );
+   v3_cross( v0, v1, n );
+   v3_normalize( n );
+
+   int count = rb_capsule__manifold_done( mtxA, c, &manifold, buf );
+   for( int i=0; i<count; i++ )
+      v3_copy( n, buf[i].n );
+
+   return count;
+}
+
 /*
  * Generates up to two contacts; optimised for the most stable manifold
  */
+__attribute__ ((deprecated))
 VG_STATIC int rb_capsule_triangle( rigidbody *rba, rigidbody *rbb, 
                                    v3f tri[3], rb_ct *buf )
 {
@@ -1595,39 +1711,48 @@ VG_STATIC int rb_capsule_triangle( rigidbody *rba, rigidbody *rbb,
    return count;
 }
 
-VG_STATIC int rb_capsule_scene( rigidbody *rba, rigidbody *rbb, rb_ct *buf )
+/* mtxB is defined only for tradition; it is not used currently */
+VG_STATIC int rb_capsule__scene( m4x3f mtxA, rb_capsule *c,
+                                 m4x3f mtxB, rb_scene *s, 
+                                 rb_ct *buf )
 {
-#if 0
-   float h = rba->inf.capsule.height,
-         r = rba->inf.capsule.radius,
-         g = 90.8f;
+   bh_iter it;
+   bh_iter_init( 0, &it );
+   int idx;
+   int count = 0;
+
+   boxf bbx;
+   v3_sub( mtxA[3], (v3f){ c->height, c->height, c->height }, bbx[0] );
+   v3_add( mtxA[3], (v3f){ c->height, c->height, c->height }, bbx[1] );
+   
+   scene *sc = s->bh_scene->user;
    
-   v3f p[2];
-   v3_muladds( rba->co, rba->up, -h*0.5f+r, p[0] );
-   v3_muladds( rba->co, rba->up,  h*0.5f-r, p[1] );
+   while( bh_next( s->bh_scene, &it, bbx, &idx ) )
+   {
+      u32 *ptri = &sc->arrindices[ idx*3 ];
+      v3f tri[3];
 
-   int count = 0;
+      for( int j=0; j<3; j++ )
+         v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
+      
+      buf[ count ].element_id = ptri[0];
 
+      int contact = rb_capsule__triangle( mtxA, c, tri, &buf[count] );
+      count += contact;
 
-   for( int i=0; i<2; i++ )
-   {
-      if( p[i][1] < g + r )
+      if( count == 16 )
       {
-         rb_ct *ct = &buf[ count ++ ];
-
-         v3_copy( p[i], ct->co );
-         ct->p = r - (p[i][1]-g);
-         ct->co[1] -= r;
-         v3_copy( (v3f){0.0f,1.0f,0.0f}, ct->n );
-         ct->rba = rba;
-         ct->rbb = rbb;
-         ct->type = k_contact_type_default;
+         vg_warn("Exceeding capsule_vs_scene capacity. Geometry too dense!\n");
+         return count;
       }
    }
 
    return count;
+}
 
-#else
+__attribute__ ((deprecated))
+VG_STATIC int rb_capsule_scene( rigidbody *rba, rigidbody *rbb, rb_ct *buf )
+{
    scene *sc = rbb->inf.scene.bh_scene->user;
 
    bh_iter it;
@@ -1663,7 +1788,6 @@ VG_STATIC int rb_capsule_scene( rigidbody *rba, rigidbody *rbb, rb_ct *buf )
    }
 
    return count;
-#endif
 }
 
 VG_STATIC int rb_scene_capsule( rigidbody *rba, rigidbody *rbb, rb_ct *buf )
@@ -1755,6 +1879,19 @@ VG_STATIC rb_ct *rb_global_ct(void)
    return rb_contact_buffer + rb_contact_count;
 }
 
+VG_STATIC void rb_prepare_contact( rb_ct *ct )
+{
+   ct->bias = -0.2f * k_rb_rate * vg_minf( 0.0f, -ct->p+k_penetration_slop );
+   rb_tangent_basis( ct->n, ct->t[0], ct->t[1] );
+
+#if 0
+   ct->type = k_contact_type_default;
+#endif
+   ct->norm_impulse = 0.0f;
+   ct->tangent_impulse[0] = 0.0f;
+   ct->tangent_impulse[1] = 0.0f;
+}
+
 /*
  * Initializing things like tangent vectors
  */
@@ -1763,16 +1900,7 @@ VG_STATIC void rb_presolve_contacts( rb_ct *buffer, int len )
    for( int i=0; i<len; i++ )
    {
       rb_ct *ct = &buffer[i];
-
-      ct->bias = -0.2f * k_rb_rate * vg_minf( 0.0f, -ct->p+k_penetration_slop );
-      rb_tangent_basis( ct->n, ct->t[0], ct->t[1] );
-
-#if 0
-      ct->type = k_contact_type_default;
-#endif
-      ct->norm_impulse = 0.0f;
-      ct->tangent_impulse[0] = 0.0f;
-      ct->tangent_impulse[1] = 0.0f;
+      rb_prepare_contact( ct );
 
       v3f ra, rb, raCn, rbCn, raCt, rbCt;
       v3_sub( ct->co, ct->rba->co, ra );
index 272cff9ba4df78002bbb512041558270760bdfe2..ab24550521a7779045192bcb0bc1cb614d1f7899 100644 (file)
 #include "render.h"
 #include "audio.h"
 #include "world.h"
+
+#if 0
 #include "player.h"
+#else
+#include "player_interface.h"
+#include "player_device_walk.h"
+#include "player_model.h"
+
+VG_STATIC player_interface localplayer;
+VG_STATIC struct player_device_walk localplayer_walk;
+
+#endif
+
 #include "network.h"
+
+#if 0
 #include "menu.h"
+#endif
 #include "vehicle.h"
 
 static int cl_ui      = 1,
@@ -46,6 +61,58 @@ VG_STATIC void vg_launch_opt(void)
 
 }
 
+VG_STATIC int __respawn( int argc, const char *argv[] )
+{
+   struct respawn_point *rp = NULL, *r;
+
+   if( argc == 1 )
+   {
+      for( int i=0; i<world.spawn_count; i++ )
+      {
+         r = &world.spawns[i];
+         if( !strcmp( r->name, argv[0] ) )
+         {
+            rp = r;
+            break;
+         }
+      }
+
+      if( !rp )
+         vg_warn( "No spawn named '%s'\n", argv[0] );
+   }
+
+   if( !rp )
+   {
+      float min_dist = INFINITY;
+
+      for( int i=0; i<world.spawn_count; i++ )
+      {
+         r = &world.spawns[i];
+         float d = v3_dist2( r->co, localplayer.rb.co );
+         
+         vg_info( "Dist %s : %f\n", r->name, d );
+         if( d < min_dist )
+         {
+            min_dist = d;
+            rp = r;
+         }
+      }
+   }
+
+   if( !rp )
+   {
+      vg_error( "No spawn found\n" );
+
+      if( !world.spawn_count )
+         return 0;
+
+      rp = &world.spawns[0];
+   }
+
+   player_spawn( &localplayer, rp );
+   return 1;
+}
+
 VG_STATIC void vg_preload(void)
 {
    g_conf_init();
@@ -74,6 +141,12 @@ VG_STATIC void vg_preload(void)
       .persistent = 0
    });
 
+   vg_function_push( (struct vg_cmd) {
+      .name = "respawn",
+      .function = __respawn,
+      //.poll_suggest = reset_player_poll
+   });
+
 vg_info(" Copyright  .        . .       -----, ,----- ,---.   .---.  \n" );
 vg_info(" 2021-2022  |\\      /| |           /  |      |    | |    /| \n" );
 vg_info("            | \\    / | +--        /   +----- +---'  |   / | \n" );
@@ -96,10 +169,15 @@ vg_info("            '        ' '--' [] '----- '----- '     ' '---'  "
 VG_STATIC void vg_load(void)
 {
    vg_loader_step( render_init, NULL );
-   vg_loader_step( menu_init, NULL );
+   //vg_loader_step( menu_init, NULL );
    vg_loader_step( world_init, NULL );
-   vg_loader_step( player_init, NULL );
-   vg_loader_step( vehicle_init, NULL );
+   //vg_loader_step( player_init, NULL );
+   //vg_loader_step( vehicle_init, NULL );
+   //
+   vg_loader_step( player_model_init, NULL );
+
+   player_interface_create_player( &localplayer );
+   player_use_device( &localplayer, &player_device_walk, &localplayer_walk );
 
    vg_bake_shaders();
    vg_loader_step( audio_init, audio_free );
@@ -114,7 +192,7 @@ VG_STATIC void vg_load(void)
 
 VG_STATIC void vg_start(void)
 {
-   reset_player( 1, (const char *[]){ "start" } );
+   __respawn( 1, (const char *[]){ "start" } );
 }
 
 VG_STATIC void draw_origin_axis(void)
@@ -133,10 +211,13 @@ VG_STATIC void vg_update(void)
       draw_origin_axis();
       network_update();
       
+#if 0
       if( !gzoomer.inside )
          player_update_pre();
+#endif
 
-      world_update( player.phys.rb.co );
+      player_pre_update( &localplayer );
+      world_update( localplayer.rb.co );
    }
 }
 
@@ -144,10 +225,14 @@ VG_STATIC void vg_update_fixed(void)
 {
    if( vg.is_loaded )
    {
+#if 0
       if( !gzoomer.inside )
          player_update_fixed();
 
       vehicle_update_fixed();
+#endif
+
+      player_update( &localplayer );
    }
 }
 
@@ -155,6 +240,7 @@ VG_STATIC void vg_update_post(void)
 {
    if( vg.is_loaded )
    {
+#if 0
       if( gzoomer.inside )
       {
          vehicle_camera();
@@ -163,8 +249,14 @@ VG_STATIC void vg_update_post(void)
       {
          player_update_post();
       }
+#endif
+
+      player_post_update( &localplayer );
+
+#if 0
       menu_update();
       vehicle_update_post();
+#endif
    }
 }
 
@@ -190,7 +282,8 @@ VG_STATIC void present_view_with_post_processing(void)
       shader_blitblur_uBlurStrength(cl_blur_strength / (vg.frame_delta*60.0f));
 
       v2f menu_blurring;
-      v2_muls( (v2f){ 0.04f, 0.001f }, menu_opacity, menu_blurring );
+      //v2_muls( (v2f){ 0.04f, 0.001f }, menu_opacity, menu_blurring );
+      v2_muls( (v2f){ 0.04f, 0.001f }, 0.0f, menu_blurring );
       shader_blitblur_uOverrideDir( menu_blurring );
 
       if( cl_view_id == 0 )
@@ -230,7 +323,9 @@ VG_STATIC void render_player_transparent(void)
    /* Draw player to window buffer and blend background ontop */
 
    glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+#if 0
    draw_player( &small_cam );
+#endif
 }
 
 VG_STATIC void render_scene(void)
@@ -244,42 +339,60 @@ VG_STATIC void render_scene(void)
 
    render_world( &main_camera );
 
+#if 0
    int player_transparent = !(player.is_dead || freecam),
        player_draw        = !cl_menu;
 
    if( !player_transparent && player_draw )
       draw_player( &main_camera );
+#endif
 
    render_water_texture( &main_camera );
    render_fb_bind( gpipeline.fb_main );
    render_water_surface( &main_camera );
    render_world_gates( &main_camera );
 
+#if 0
    if( player_transparent && player_draw )
       render_player_transparent();
+#endif
 }
 
 VG_STATIC void render_menu(void)
 {
    glClear( GL_DEPTH_BUFFER_BIT );
+#if 0
    menu_render( &main_camera );
+#endif
 }
 
 VG_STATIC void render_main_game(void)
 {
+#if 0
    static float fov = 60.0f;
    float fov_target = vg_lerpf( 90.0f, 110.0f, cl_fov );
-   if( player.phys.on_board )
+
+   if( player.controller == k_player_controller_skate )
       fov_target = vg_lerpf( 97.0f, 135.0f, cl_fov );
+
    if( cl_menu )
       fov_target = menu_fov_target;
    fov = vg_lerpf( fov, fov_target, vg.frame_delta * 2.0f );
    fov = freecam? 60.0f: fov;
 
    main_camera.fov   = fov;
+#endif
+
+   /* copy camera from player.
+    * TODO: blend with camera from menu */
+
+   v3_copy( localplayer.cam.pos, main_camera.pos );
+   v3_copy( localplayer.cam.angles, main_camera.angles );
+   main_camera.fov = localplayer.cam.fov;
    main_camera.nearz = 0.1f;
    main_camera.farz  = 2100.0f;
 
+   camera_update_transform( &main_camera );
    camera_update_view( &main_camera );
    camera_update_projection( &main_camera );
    camera_finalize( &main_camera );
@@ -289,11 +402,13 @@ VG_STATIC void render_main_game(void)
    render_scene();
    present_view_with_post_processing();
 
+#if 0
    if( cl_menu ) 
    {
       render_menu();
       render_player_transparent();
    }
+#endif
 
    /* =========== End Frame =========== */
 }
@@ -320,7 +435,11 @@ VG_STATIC void vg_render(void)
 VG_STATIC void run_light_widget( struct light_widget *lw );
 VG_STATIC void vg_ui(void)
 {
+   player_ui( &localplayer );
+
+#if 0
    menu_crap_ui();
+#endif
 
    if( cl_light_edit )
    {
@@ -366,7 +485,10 @@ VG_STATIC void vg_ui(void)
    
    audio_debug_soundscapes();
    render_view_framebuffer_ui();
+
+#if 0
    player_physics_gui();
+#endif
 }
 
 VG_STATIC void run_light_widget( struct light_widget *lw )
@@ -389,9 +511,10 @@ VG_STATIC void run_light_widget( struct light_widget *lw )
 
 VG_STATIC void run_debug_info(void)
 {
+#if 0
    char buf[40];
    
-   snprintf( buf, 40, "%.2fm/s", v3_length( player.phys.rb.v ) );
+   snprintf( buf, 40, "%.2fm/s", v3_length( player.rb.v ) );
    ui_text( (ui_px [2]){ 0, 0 }, buf, 1, k_text_align_left );
    
    snprintf( buf, 40, "%.2f %.2f %.2f m/s", 
@@ -415,4 +538,5 @@ VG_STATIC void run_debug_info(void)
       ui_text( (ui_px [2]){ 0, 60 }, 
             "Gamepad not ready", 1, k_text_align_left );
    }
+#endif
 }
index 4caf21cbbc91f1ad15fe553427bda84a674f717d..06c8be1f9d1301be106921f670931e6ef4c21c54 100644 (file)
--- a/vehicle.h
+++ b/vehicle.h
@@ -1,3 +1,5 @@
+
+#define VEHICLE_H
 #ifndef VEHICLE_H
 #define VEHICLE_H
 
@@ -292,7 +294,7 @@ VG_STATIC void vehicle_update_post(void)
       return;
 
    rb_debug( &gzoomer.rb, VG__WHITE );
-   vg_line( player.phys.rb.co, gzoomer.rb.co, VG__WHITE );
+   vg_line( player.rb.co, gzoomer.rb.co, VG__WHITE );
 
    /* draw friction vectors */
    v3f p0, px, py;
diff --git a/world.h b/world.h
index e158e184e4b9f89193d0552842c75fde21e2082a..5b27ef7951b7479ad9c715bef7abafb5f366e65c 100644 (file)
--- a/world.h
+++ b/world.h
@@ -326,7 +326,7 @@ VG_STATIC struct gworld
           mesh_water;
 
    mdl_submesh sm_foliage_main;
-   rigidbody rb_geo;
+   rigidbody rb_geo; /* todo.. ... */
 }
 world;
 
@@ -650,6 +650,71 @@ VG_STATIC int ray_world( v3f pos, v3f dir, ray_hit *hit )
    return scene_raycast( world.scene_geo, world.geo_bh, pos, dir, hit );
 }
 
+/*
+ * 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 )
+{
+   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 );
+
+   v3f dir_inv;
+   dir_inv[0] = 1.0f/dir[0];
+   dir_inv[1] = 1.0f/dir[1];
+   dir_inv[2] = 1.0f/dir[2];
+
+   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_aabb1( box, pa, dir_inv, 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;
+}
+
 VG_STATIC struct world_material *world_tri_index_material( u32 index )
 {
    for( int i=1; i<world.material_count; i++ )
index c0c4264898d1b6ef4db16c5255851d5cf47acbc7..3a66523991d09c1efac6812fb7c48a40c8b6532e 100644 (file)
@@ -593,11 +593,12 @@ VG_STATIC void world_post_process(void)
 
    vg_release_thread_sync();
 
+#if 0
    /*
     * Setup scene collider 
     */
-
    reset_player( 1, (const char *[]){"start"} );
+#endif
 }
 
 VG_STATIC void world_process_resources(void)