the never ending refactor
authorhgn <hgodden00@gmail.com>
Thu, 2 Feb 2023 10:38:41 +0000 (10:38 +0000)
committerhgn <hgodden00@gmail.com>
Thu, 2 Feb 2023 10:38:41 +0000 (10:38 +0000)
22 files changed:
.gitignore
camera.h
player.c [new file with mode: 0644]
player.h
player_api.h [new file with mode: 0644]
player_common.c [new file with mode: 0644]
player_common.h [new file with mode: 0644]
player_device_common.h [deleted file]
player_device_dead.h [deleted file]
player_device_skate.h [deleted file]
player_device_walk.h [deleted file]
player_interface.h
player_model.h
player_physics.h [deleted file]
player_physics_skate.h [deleted file]
player_physics_walk.h [deleted file]
player_skate.c [new file with mode: 0644]
player_skate.h [new file with mode: 0644]
player_walk.c [new file with mode: 0644]
player_walk.h [new file with mode: 0644]
player_walkgrid.h [deleted file]
skaterift.c

index 510138c2a2960889413c10c2b36c624fae4b63fc..d14eaf677ba4e94434fd07045504224012573a98 100755 (executable)
@@ -2,6 +2,7 @@
 *.so
 *.dll
 *.log
+*.old
 
 # soft links
 steamworks_sdk
index dbacc26afd7d89f47308781caeef7767ae0f03e4..77fc0893f4e885e6766e001617b23b2bab33bcb6 100644 (file)
--- a/camera.h
+++ b/camera.h
@@ -27,6 +27,13 @@ struct camera
 }
 VG_STATIC main_camera;
 
+VG_STATIC void camera_lerp_angles( v3f a, v3f b, float t, v3f d )
+{
+   d[0] = vg_alerpf( a[0], b[0], t );
+   d[1] = vg_lerpf(  a[1], b[1], t );
+   d[2] = vg_lerpf(  a[2], b[2], t );
+}
+
 VG_STATIC void camera_lerp( camera *a, camera *b, float t, camera *d )
 {
    v3_lerp( a->pos, b->pos, t, d->pos );
diff --git a/player.c b/player.c
new file mode 100644 (file)
index 0000000..08741d3
--- /dev/null
+++ b/player.c
@@ -0,0 +1,318 @@
+#ifndef PLAYER_C
+#define PLAYER_C
+
+#include "player.h"
+#include "camera.h"
+#include "player_model.h"
+
+PLAYER_API
+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;
+}
+
+/*
+ * Init
+ */
+PLAYER_API
+void player__create( player_instance *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 );
+   inst->input_camera=vg_create_named_input( "camera",  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",
+      "bind camera c"
+   };
+
+   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 );
+}
+
+/* 
+ * Appearence
+ */
+PLAYER_API
+void player__use_avatar( player_instance *player, struct player_avatar *av )
+{
+   player->playeravatar = av;
+   player_setup_ragdoll_from_avatar( &player->ragdoll, av );
+}
+
+PLAYER_API
+void player__use_mesh( player_instance *player, glmesh *mesh )
+{
+   player->playermesh = mesh;
+}
+
+PLAYER_API
+void player__use_texture( player_instance *player, vg_tex2d *tex )
+{
+   player->playertex = tex;
+}
+
+PLAYER_API
+void player__bind( player_instance *player )
+{
+   player__skate_bind( player );
+   player__walk_bind( player );
+}
+
+/*
+ * Gameloop events
+ * ----------------------------------------------------------------------------
+ */
+PLAYER_API
+void player__pre_update( player_instance *player )
+{
+   if( vg_input_button_down( player->input_camera ) )
+   {
+      if( player->camera_mode == k_cam_firstperson )
+         player->camera_mode = k_cam_thirdperson;
+      else
+         player->camera_mode = k_cam_firstperson;
+   }
+
+   if( _player_pre_update[ player->subsystem ] )
+      _player_pre_update[ player->subsystem ]( player );
+}
+
+PLAYER_API
+void player__update( player_instance *player )
+{
+   if( _player_update[ player->subsystem ] )
+      _player_update[ player->subsystem ]( player );
+}
+
+PLAYER_API
+void player__post_update( player_instance *player )
+{
+   if( _player_post_update[ player->subsystem ] )
+      _player_post_update[ player->subsystem ]( player );
+}
+
+VG_STATIC void player_apply_transport_to_cam( m4x3f transport )
+{
+   /* FIXME: Applies to main_camera directly! */
+
+   /* Pre-emptively edit the camera matrices so that the motion vectors 
+    * are correct */
+   m4x3f transport_i;
+   m4x4f transport_4;
+   m4x3_invert_affine( 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 );
+}
+
+/*
+ * Applies gate transport to a player_interface
+ */
+PLAYER_API
+void player__pass_gate( player_instance *player, teleport_gate *gate )
+{
+   player->gate_waiting = gate;
+
+   /* TODO: Add back other logic thats normally here */
+}
+
+VG_STATIC void player_camera_portal_correction( player_instance *player )
+{
+   if( player->gate_waiting )
+   {
+      /* construct plane equation for reciever gate */
+      v4f plane;
+      v3_copy( player->gate_waiting->recv_to_world[2], plane );
+      plane[3] = v3_dot( plane, player->gate_waiting->recv_to_world[3] );
+
+      /* check camera polarity */
+      if( v3_dot( player->cam.pos, plane ) < plane[3] ) 
+      {
+         vg_success( "Plane cleared\n" );
+         player_apply_transport_to_cam( player->gate_waiting->transport );
+         player->gate_waiting = NULL;
+      }
+      else
+      {
+         /* de-transform camera and player back */
+         m4x3f inverse;
+         m4x3_invert_affine( player->gate_waiting->transport, inverse );
+         m4x3_mulv( inverse, player->cam.pos, player->cam.pos );
+
+         /* TODO: Find robust method for this */
+         v3f fwd_dir = { cosf(player->cam.angles[0]),
+                         0.0f,
+                         sinf(player->cam.angles[0])};
+         m3x3_mulv( inverse, fwd_dir, fwd_dir );
+         player->cam.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
+
+         struct skeleton *sk = &player->playeravatar->sk;
+         skeleton_apply_transform( sk, inverse );
+      }
+   }
+}
+
+VG_STATIC void player__pre_render( player_instance *player )
+{
+   if( _player_animate[ player->subsystem ] )
+   {
+      player_animation res;
+      _player_animate[ player->subsystem ]( player, &res );
+
+      /* TODO: eventually, blending code goes here */
+
+      m4x3f transform;
+      q_m3x3( res.root_q, transform );
+      v3_copy( res.root_co, transform[3] );
+
+      struct skeleton *sk = &player->playeravatar->sk;
+
+      skeleton_apply_pose( sk, res.pose, k_anim_apply_defer_ik );
+      skeleton_apply_ik_pass( sk );
+      skeleton_apply_pose( sk, res.pose, k_anim_apply_deffered_only );
+      skeleton_apply_inverses( sk );
+      skeleton_apply_transform( sk, transform );
+      skeleton_debug( sk );
+   }
+
+   if( _player_post_animate[ player->subsystem ] )
+      _player_post_animate[ player->subsystem ]( player );
+
+   /* TODO: eventually, blending code goes here */
+
+   float camera_blend_target = 1.0f;
+   if( player->camera_mode == k_cam_firstperson )
+      camera_blend_target = 0.0f;
+
+   player->camera_type_blend = vg_lerpf( player->camera_type_blend, 
+                                         camera_blend_target,
+                                         5.0f * vg.frame_delta );
+
+   float t = player->camera_type_blend;
+   camera_lerp_angles( player->cam1.angles, player->cam3.angles, 
+                       t, player->cam.angles );
+   v3_lerp( player->cam1.co, player->cam3.co, t, player->cam.pos );
+   player->cam.fov = vg_lerpf( 118.0f, 90.0f, t );
+
+   player_camera_portal_correction( player );
+}
+
+PLAYER_API void player__render( camera *cam, player_instance *player )
+{
+   shader_viewchar_use();
+   vg_tex2d_bind( player->playertex, 0 );
+   shader_viewchar_uTexMain( 0 );
+   shader_viewchar_uCamera( cam->transform[3] );
+   shader_viewchar_uPv( cam->mtx.pv );
+   shader_link_standard_ub( _shader_viewchar.id, 2 );
+   glUniformMatrix4x3fv( _uniform_viewchar_uTransforms, 
+                         player->playeravatar->sk.bone_count,
+                         0,
+                         (float *)player->playeravatar->sk.final_mtx );
+   
+   mesh_bind( player->playermesh );
+   mesh_draw( player->playermesh );
+}
+
+PLAYER_API void player__im_gui( player_instance *player )
+{
+   vg_uictx.cursor[0] = vg.window_x - 200;
+   vg_uictx.cursor[1] = 0;
+   vg_uictx.cursor[2] = 200;
+   vg_uictx.cursor[3] = 200;
+
+   struct ui_vert *b = ui_fill_rect( vg_uictx.cursor, 0x70000000 );
+
+   vg_uictx.cursor[0] = vg.window_x;
+
+   if( _player_im_gui[ player->subsystem ] )
+      _player_im_gui[ player->subsystem ]( player );
+
+   b[2].co[1] = vg_uictx.cursor[1];
+   b[3].co[1] = vg_uictx.cursor[1];
+}
+
+PLAYER_API void player__spawn( player_instance *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 );
+   rb_update_transform( &player->rb );
+
+   if( _player_reset[ player->subsystem ] )
+      _player_reset[ player->subsystem ]( player, rp );
+}
+
+
+PLAYER_API void player__kill( player_instance *player )
+{
+   
+}
+
+#endif /* PLAYER_C */
index 1a97e1a2b0de8ace2b91be91618fdf586ae2d36b..c9f13f729bd8953220694f15f5ac22419f9fd99a 100644 (file)
--- a/player.h
+++ b/player.h
@@ -1,3 +1,168 @@
+#ifndef PLAYER_H
+#define PLAYER_H
+
+#include "player_api.h"
+
+#include "player_common.h"
+#include "player_walk.h"
+#include "player_skate.h"
+//#include "player_dead.h"
+
+struct player_instance
+{
+   /* transform definition */
+   rigidbody rb;
+   v3f angles;
+
+   /*
+    * Camera management
+    * ---------------------------
+    */
+   camera cam; /* output final camera */
+
+   enum camera_mode
+   {
+      k_cam_firstperson = 0,
+      k_cam_thirdperson = 1
+   }
+   camera_mode;
+   float camera_type_blend;
+
+   struct
+   {
+      v3f co, angles;
+   }
+   cam1, cam3;
+
+   teleport_gate *gate_waiting;
+
+   /*
+    * Input 
+    * --------------------------------
+    */
+   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,
+                        *input_camera;
+
+   /*
+    * Animation
+    * --------------------------------------------------
+    */
+
+   struct player_avatar  *playeravatar;
+   glmesh                *playermesh;
+   struct player_ragdoll  ragdoll;
+   vg_tex2d              *playertex;
+
+   /*
+    * Subsystems
+    * -------------------------------------------------
+    */
+
+   enum player_subsystem
+   {
+      k_player_subsystem_walk = 0,
+      k_player_subsystem_skate = 1,
+      k_player_subsystem_dead = 2
+   }
+   subsystem;
+
+   struct player_skate  _skate;
+   struct player_walk   _walk;
+   //struct player_dead   _dead;
+};
+
+/*
+ * Gameloop tables
+ * ---------------------------------------------------------
+ */
+
+VG_STATIC
+void (*_player_bind[])( player_instance *player ) =
+{
+   player__walk_bind,
+   player__skate_bind,
+   NULL
+};
+
+VG_STATIC
+void (*_player_reset[])( player_instance *player, struct respawn_point *rp ) =
+{
+   NULL,
+   player__skate_reset,
+   NULL
+};
+
+VG_STATIC
+void (*_player_pre_update[])( player_instance *player ) = 
+{
+   player__walk_pre_update,
+   player__skate_pre_update,
+   NULL
+};
+
+VG_STATIC
+void( *_player_update[])( player_instance *player ) =
+{
+   player__walk_update,
+   player__skate_update,
+   NULL
+};
+
+VG_STATIC 
+void( *_player_post_update[])( player_instance *player ) =
+{
+   player__walk_post_update,
+   player__skate_post_update,
+   NULL
+};
+
+VG_STATIC
+void( *_player_im_gui[])( player_instance *player ) =
+{
+   player__walk_im_gui,
+   player__skate_im_gui,
+   NULL
+};
+
+VG_STATIC
+void( *_player_animate[])( player_instance *player, player_animation *dest ) =
+{
+   player__walk_animate,
+   player__skate_animate,
+   NULL
+};
+
+VG_STATIC
+void( *_player_post_animate[])( player_instance *player ) =
+{
+   player__walk_post_animate,
+   player__skate_post_animate,
+   NULL
+};
+
+/* implementation */
+
+#include "player.c"
+#include "player_common.c"
+#include "player_walk.c"
+#include "player_skate.c"
+//#include "player_dead.c"
+
+#endif /* PLAYER_H */
+
+
+#if 0
 /*
  * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
@@ -908,3 +1073,4 @@ VG_STATIC void player_restore_frame(void)
 }
 
 #endif /* PLAYER_H */
+#endif
diff --git a/player_api.h b/player_api.h
new file mode 100644 (file)
index 0000000..75c3873
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef PLAYER_API_H
+#define PLAYER_API_H
+
+#define VG_GAME
+#include "vg/vg.h"
+
+#include "player_ragdoll.h"
+#include "player_model.h"
+
+/*
+ * Defines a set of routines used to interact with the player
+ */
+
+#define PLAYER_API VG_STATIC
+typedef struct player_instance player_instance;
+typedef mdl_keyframe player_pose[32];
+typedef struct player_animation player_animation;
+
+struct player_animation
+{
+#if 0
+   camera camera_firstperson,
+          camera_thirdperson;
+#endif
+
+   player_pose pose;
+   v3f         root_co;
+   v4f         root_q;
+};
+
+/*
+ * Init
+ */
+PLAYER_API void player_create       ( player_instance *player );
+
+/* 
+ * Appearence
+ */
+PLAYER_API void player_use_avatar   ( player_instance *player,
+                                      struct player_avatar *av );
+PLAYER_API void player_use_mesh     ( player_instance *player, glmesh *mesh );
+PLAYER_API void player_use_texture  ( player_instance *player, vg_tex2d *tex );
+
+
+/*
+ * Gameloop events
+ * ----------------------------------------------------------------------------
+ */
+PLAYER_API void player__bind        ( player_instance *player );
+PLAYER_API void player__pre_update  ( player_instance *player );
+PLAYER_API void player__update      ( player_instance *player );
+PLAYER_API void player__post_update ( player_instance *player );
+PLAYER_API void player__pre_render  ( player_instance *player );
+PLAYER_API void player__render      ( camera *cam, player_instance *player );
+PLAYER_API void player__im_gui      ( player_instance *player );
+
+/*
+ * Mechanic events
+ * ----------------------------------------------------------------------------
+ */
+PLAYER_API void player__spawn       ( player_instance *player,
+                                      struct respawn_point *rp );
+PLAYER_API void player__kill        ( player_instance *player );
+PLAYER_API void player__pass_gate   ( player_instance *player,
+                                      teleport_gate *gate );
+
+/*
+ * Utiltiy
+ * ----------------------------------------------------------------------------
+ */
+PLAYER_API void player__debugtext( int size, const char *fmt, ... );
+
+#endif /* PLAYER_API_H */
diff --git a/player_common.c b/player_common.c
new file mode 100644 (file)
index 0000000..69d35ab
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef PLAYER_COMMON_C
+#define PLAYER_COMMON_C
+
+#include "player.h"
+
+void player_look( player_instance *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_COMMON_C */
diff --git a/player_common.h b/player_common.h
new file mode 100644 (file)
index 0000000..91298fc
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef PLAYER_COMMON_H
+#define PLAYER_COMMON_H
+
+#include "player_api.h"
+
+void player_look( player_instance *player, v3f angles );
+
+#endif /* PLAYER_COMMON_H */
diff --git a/player_device_common.h b/player_device_common.h
deleted file mode 100644 (file)
index 07a3d87..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef PLAYER_DEVICE_COMMON_H
-#define PLAYER_DEVICE_COMMON_H
-
-#define VG_GAME
-#include "vg/vg.h"
-#include "common.h"
-#include "player_interface.h"
-
-struct device_transition_skateboard
-{
-   v3f dir;
-};
-
-struct device_transition_walk
-{
-   v3f angles;
-};
-
-#endif /* PLAYER_DEVICE_COMMON_H */
diff --git a/player_device_dead.h b/player_device_dead.h
deleted file mode 100644 (file)
index 5605571..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef PLAYER_DEVICE_DEAD_H
-#define PLAYER_DEVICE_DEAD_H
-
-#include "player_interface.h"
-#include "skeleton.h"
-#include "player_model.h"
-
-VG_STATIC
-struct player_device_dead
-{
-   int _;
-}
-localplayer_device_dead;
-
-VG_STATIC int player_dead_event( player_device *dev, player_interface *player,
-                                 enum player_device_event_type ev, void *data )
-{
-   if( ev == k_player_device_event_update )
-   {
-      player_ragdoll_iter( &player->ragdoll );
-   }
-   else if( ev == k_player_device_event_custom_transition )
-   {
-      copy_avatar_pose_to_ragdoll( player->playeravatar, &player->ragdoll, 
-                                   player->rb.v );
-   }
-   else if( ev == k_player_device_event_animate )
-   {
-      v3_zero( dev->pose_root_co );
-      q_identity( dev->pose_root_q );
-      
-      for( int i=0; i<vg_list_size( dev->pose ); i ++ )
-      {
-         v3_zero( dev->pose[i].co );
-         v3_fill( dev->pose[i].s, 1.0f );
-         q_identity( dev->pose[i].q );
-      }
-   }
-   else if( ev == k_player_device_event_post_animate )
-   {
-      struct player_avatar *av = player->playeravatar;
-
-      v3_zero( dev->cam_1st.pos );
-      v3_zero( dev->cam_1st.angles );
-      dev->cam_1st.fov = 90.0f;
-      
-      /* FIXME: This overwrites pose blending, however, do we need to blend with 
-       * this device, anyway? */
-      copy_ragdoll_pose_to_avatar( &player->ragdoll, player->playeravatar );
-   }
-   else
-      return 0;
-
-   return 1;
-}
-
-VG_STATIC player_device player_device_dead =
-{
-   .name          = "ragdoll/dead",
-   .event         = player_dead_event,
-   .storage       = &localplayer_device_dead
-};
-
-#endif /* PLAYER_DEVICE_DEAD_H */
diff --git a/player_device_skate.h b/player_device_skate.h
deleted file mode 100644 (file)
index d657b4a..0000000
+++ /dev/null
@@ -1,1769 +0,0 @@
-#ifndef PLAYER_DEVICE_SKATE_H
-#define PLAYER_DEVICE_SKATE_H
-
-#include "player_interface.h"
-#include "skeleton.h"
-#include "player_model.h"
-#include "player_device_common.h"
-
-VG_STATIC
-struct player_device_skate
-{
-   struct
-   {
-      enum skate_activity
-      {
-         k_skate_activity_air,
-         k_skate_activity_ground,
-         k_skate_activity_grind
-      }
-      activity,
-      activity_prev;
-
-      float steery,
-            steerx,
-            steery_s,
-            steerx_s,
-            reverse,
-            slip;
-
-      v3f   flip_axis;
-      float flip_time,
-            flip_rate;
-
-      m3x3f velocity_bias,
-            velocity_bias_pstep;
-      v3f apex;
-
-      int lift_frames;
-
-      v3f throw_v;
-      v3f cog_v, cog;
-
-      float grabbing;
-      v2f grab_mouse_delta;
-
-      int charging_jump, jump_dir;
-      float jump_charge;
-      double jump_time;
-
-      double start_push,
-             cur_push;
-
-      v3f prev_pos;
-
-      /* FIXME: Sensible names */
-      v3f vl,          /* 1st */
-          posl, dirl;  /* 3rd */
-   }
-   state,
-   state_gate_storage;
-
-   struct land_prediction
-   {
-      v3f   log[50];
-      v3f   n;
-      v3f   apex;
-      u32   log_length;
-      float score,
-            land_dist;
-
-      enum prediction_type
-      {
-         k_prediction_none,
-         k_prediction_land,
-         k_prediction_grind
-      }
-      type;
-
-      u32   colour;
-   }
-   predictions[22];
-   u32 prediction_count;
-   float land_dist;
-   v3f land_normal;
-
-   /* animation */
-   struct skeleton_anim *anim_stand, *anim_highg, *anim_slide,
-                        *anim_air,
-                        *anim_push,  *anim_push_reverse,
-                        *anim_ollie, *anim_ollie_reverse,
-                        *anim_grabs, *anim_stop;
-   rb_sphere sphere_front, sphere_back;
-   v3f board_offset;
-   v4f board_rotation;
-
-   float blend_slide,
-         blend_z,
-         blend_x,
-         blend_fly,
-         blend_stand,
-         blend_push,
-         blend_jump,
-         blend_airdir;
-
-   v2f wobble;
-
-   float debug_normal_pressure;
-   u32 device_id_walk;
-}
-localplayer_device_skate;
-
-VG_STATIC void player_skate_bind( player_device *dev,
-                                  player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-   struct skeleton *sk = &av->sk;
-
-   rb_update_transform( &player->rb );
-   s->anim_stand           = skeleton_get_anim( sk, "pose_stand" );
-   s->anim_highg           = skeleton_get_anim( sk, "pose_highg" );
-   s->anim_air             = skeleton_get_anim( sk, "pose_air" );
-   s->anim_slide           = skeleton_get_anim( sk, "pose_slide" );
-   s->anim_push            = skeleton_get_anim( sk, "push" );
-   s->anim_push_reverse    = skeleton_get_anim( sk, "push_reverse" );
-   s->anim_ollie           = skeleton_get_anim( sk, "ollie" );
-   s->anim_ollie_reverse   = skeleton_get_anim( sk, "ollie_reverse" );
-   s->anim_grabs           = skeleton_get_anim( sk, "grabs" );
-
-   s->device_id_walk = player_get_device( player, "walk" );
-}
-
-/* 
- * Collision detection routines
- *
- *
- */
-
-/*
- * Does collision detection on a sphere vs world, and applies some smoothing
- * filters to the manifold afterwards
- */
-VG_STATIC int skate_collide_smooth( player_interface *player,
-                                    m4x3f mtx, rb_sphere *sphere,
-                                    rb_ct *man )
-{
-   debug_sphere( mtx, sphere->radius, VG__BLACK );
-
-   int len = 0;
-   len = rb_sphere__scene( mtx, sphere, NULL, &world.rb_geo.inf.scene, man );
-
-   for( int i=0; i<len; i++ )
-   {
-      man[i].rba = &player->rb;
-      man[i].rbb = NULL;
-   }
-
-   rb_manifold_filter_coplanar( man, len, 0.05f );
-
-   if( len > 1 )
-   {
-      rb_manifold_filter_backface( man, len );
-      rb_manifold_filter_joint_edges( man, len, 0.05f );
-      rb_manifold_filter_pairs( man, len, 0.05f );
-   }
-   int new_len = rb_manifold_apply_filtered( man, len );
-   if( len && !new_len )
-      len = 1;
-   else
-      len = new_len;
-
-   return len;
-}
-/*
- * Gets the closest grindable edge to the player within max_dist 
- */
-VG_STATIC struct grind_edge *skate_collect_grind_edge( v3f p0, v3f p1,
-                                                       v3f c0, v3f c1, 
-                                                       float max_dist )
-{
-   bh_iter it;
-   bh_iter_init( 0, &it );
-
-   boxf region;
-
-   box_init_inf( region );
-   box_addpt( region, p0 );
-   box_addpt( region, p1 );
-   
-   float k_r = max_dist;
-   v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] );
-   v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] );
-
-   float closest = k_r*k_r;
-   struct grind_edge *closest_edge = NULL;
-   
-   int idx;
-   while( bh_next( world.grind_bh, &it, region, &idx ) )
-   {
-      struct grind_edge *edge = &world.grind_edges[ idx ];
-
-      float s,t;
-      v3f pa, pb;
-
-      float d2 = 
-         closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb );
-
-      if( d2 < closest )
-      {
-         closest = d2;
-         closest_edge = edge;
-         v3_copy( pa, c0 );
-         v3_copy( pb, c1 );
-      }
-   }
-
-   return closest_edge;
-}
-
-VG_STATIC int skate_grind_collide( player_device *dev,
-                                   player_interface *player, 
-                                   rb_ct *contact )
-{
-   v3f p0, p1, c0, c1;
-   v3_muladds( player->rb.co, player->rb.to_world[2],  0.5f, p0 );
-   v3_muladds( player->rb.co, player->rb.to_world[2], -0.5f, p1 );
-   v3_muladds( p0, player->rb.to_world[1], 0.125f-0.15f, p0 );
-   v3_muladds( p1, player->rb.to_world[1], 0.125f-0.15f, p1 );
-
-   float const k_r = 0.25f;
-   struct grind_edge *closest_edge = skate_collect_grind_edge( p0, p1, 
-                                                               c0, c1, k_r );
-
-   if( closest_edge )
-   {
-      v3f delta;
-      v3_sub( c1, c0, delta );
-
-      if( v3_dot( delta, player->rb.to_world[1] ) > 0.0001f )
-      {
-         contact->p = v3_length( delta );
-         contact->type = k_contact_type_edge;
-         contact->element_id = 0;
-         v3_copy( c1, contact->co );
-         contact->rba = NULL;
-         contact->rbb = NULL;
-
-         v3f edge_dir, axis_dir;
-         v3_sub( closest_edge->p1, closest_edge->p0, edge_dir );
-         v3_normalize( edge_dir );
-         v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
-         v3_cross( edge_dir, axis_dir, contact->n );
-
-         return 1;
-      }
-      else
-         return 0;
-   }
-
-   return 0;
-}
-
-/*
- *
- * Prediction system
- *
- *
- */
-
-/* 
- * Trace a path given a velocity rotation.
- *
- * TODO: this MIGHT be worth doing RK4 on the gravity field.
- */
-VG_STATIC void skate_score_biased_path( v3f co, v3f v, m3x3f vr, 
-                                        struct land_prediction *prediction )
-{
-   float pstep  = VG_TIMESTEP_FIXED * 10.0f;
-   float k_bias = 0.96f;
-
-   v3f pco, pco1, pv;
-   v3_copy( co, pco );
-   v3_muls( v,  k_bias, pv );
-
-   m3x3_mulv( vr, pv, pv );
-   v3_muladds( pco, pv, pstep, pco );
-
-   struct grind_edge *best_grind = NULL;
-   float closest_grind = INFINITY;
-
-   float grind_score    = INFINITY,
-         air_score      = INFINITY,
-         time_to_impact = 0.0f;
-
-   prediction->log_length = 0;
-   v3_copy( pco, prediction->apex );
-
-   for( int i=0; i<vg_list_size(prediction->log); i++ )
-   {
-      v3_copy( pco, pco1 );
-
-      pv[1] += -k_gravity * pstep;
-
-      m3x3_mulv( vr, pv, pv );
-      v3_muladds( pco, pv, pstep, pco );
-
-      if( pco[1] > prediction->apex[1] )
-         v3_copy( pco, prediction->apex );
-      
-      v3f vdir;
-
-      v3_sub( pco, pco1, vdir );
-
-      float l = v3_length( vdir );
-      v3_muls( vdir, 1.0f/l, vdir );
-
-      v3f c0, c1;
-      struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
-                                                        c0, c1, 0.4f );
-
-      if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
-      {
-         float d2 = v3_dist2( c0, c1 );
-         if( d2 < closest_grind )
-         {
-            closest_grind = d2;
-            best_grind = ge;
-            grind_score = closest_grind * 0.05f;
-         }
-      }
-
-      v3f n1;
-
-      float t1;
-      int idx = spherecast_world( pco1, pco, 0.4f, &t1, n1 );
-      if( idx != -1 )
-      {
-         v3_copy( n1, prediction->n );
-         air_score = -v3_dot( pv, n1 );
-         
-         u32 vert_index = world.scene_geo->arrindices[ idx*3 ];
-         struct world_material *mat = world_tri_index_material( vert_index );
-
-         /* Bias prediction towords ramps */
-         if( mat->info.flags & k_material_flag_skate_surface )
-            air_score *= 0.1f;
-
-         v3_lerp( pco1, pco, t1, prediction->log[ prediction->log_length ++ ] ); 
-         time_to_impact += t1 * pstep;
-         break;
-      }
-
-      time_to_impact += pstep;
-      v3_copy( pco, prediction->log[ prediction->log_length ++ ] );
-   }
-
-   if( grind_score < air_score )
-   {
-      prediction->score = grind_score; 
-      prediction->type = k_prediction_grind;
-   }
-   else if( air_score < INFINITY )
-   {
-      prediction->score = air_score;
-      prediction->type = k_prediction_land;
-   }
-   else
-   {
-      prediction->score = INFINITY;
-      prediction->type = k_prediction_none;
-   }
-
-   prediction->land_dist = time_to_impact;
-}
-
-VG_STATIC 
-void player_approximate_best_trajectory( player_interface *player,
-                                         struct player_device_skate *s )
-{
-   float pstep = VG_TIMESTEP_FIXED * 10.0f;
-   float best_velocity_delta = -9999.9f;
-
-   v3f axis;
-   v3_cross( player->rb.to_world[1], player->rb.v, axis );
-   v3_normalize( axis );
-
-   s->prediction_count = 0;
-   m3x3_identity( s->state.velocity_bias );
-
-   float best_vmod   = 0.0f,
-         min_score   =  INFINITY,
-         max_score   = -INFINITY;
-
-   v3_zero( s->state.apex );
-   s->land_dist = 0.0f;
-
-   /*
-    * Search a broad selection of futures
-    */
-   for( int m=-3;m<=12; m++ )
-   {
-      struct land_prediction *p = &s->predictions[ s->prediction_count ++ ];
-
-      float vmod = ((float)m / 15.0f)*0.09f;
-
-      m3x3f bias;
-      v4f bias_q;
-
-      q_axis_angle( bias_q, axis, vmod );
-      q_m3x3( bias_q, bias );
-
-      skate_score_biased_path( player->rb.co, player->rb.v, bias, p );
-
-      if( p->type != k_prediction_none )
-      {
-         if( p->score < min_score )
-         {
-            min_score = p->score;
-            best_vmod = vmod;
-            s->land_dist = p->land_dist;
-            v3_copy( p->apex, s->state.apex );
-         }
-
-         if( p->score > max_score )
-            max_score = p->score;
-      }
-   }
-
-   v4f vr_q;
-   q_axis_angle( vr_q, axis, best_vmod*0.1f );
-   q_m3x3( vr_q, s->state.velocity_bias );
-
-   q_axis_angle( vr_q, axis, best_vmod );
-   q_m3x3( vr_q, s->state.velocity_bias_pstep );
-
-   /*
-    * Logging
-    */
-   for( int i=0; i<s->prediction_count; i ++ )
-   {
-      struct land_prediction *p = &s->predictions[i];
-
-      float l = p->score;
-
-      if( l < 0.0f )
-      {
-         vg_error( "negative score! (%f)\n", l );
-      }
-
-      l -= min_score;
-      l /= (max_score-min_score);
-      l  = 1.0f - l;
-      l *= 255.0f;
-
-      p->colour = l;
-      p->colour <<= 8;
-      p->colour |= 0xff000000;
-   }
-
-
-   v2f steer = { player->input_js1h->axis.value,
-                 player->input_js1v->axis.value };
-   v2_normalize_clamp( steer );
-
-   if( (fabsf(steer[1]) > 0.5f) && (s->land_dist >= 1.0f) )
-   {
-      s->state.flip_rate = (1.0f/s->land_dist) * vg_signf(steer[1]) *
-                              s->state.reverse ;
-      s->state.flip_time = 0.0f;
-      v3_copy( player->rb.to_world[0], s->state.flip_axis );
-   }
-   else
-   {
-      s->state.flip_rate = 0.0f;
-      v3_zero( s->state.flip_axis );
-   }
-}
-
-/*
- *
- * Varius physics models
- * ------------------------------------------------
- */
-
-VG_STATIC void skate_apply_grind_model( player_interface *player,
-                                        struct player_device_skate *s,
-                                        rb_ct *manifold, int len )
-{
-   /* FIXME: Queue audio events instead */
-   if( len == 0 )
-   {
-      if( s->state.activity == k_skate_activity_grind )
-      {
-#if 0
-         audio_lock();
-         audio_player_set_flags( &audio_player_extra, 
-                                 AUDIO_FLAG_SPACIAL_3D );
-         audio_player_set_position( &audio_player_extra, player.rb.co );
-         audio_player_set_vol( &audio_player_extra, 20.0f );
-         audio_player_playclip( &audio_player_extra, &audio_board[6] );
-         audio_unlock();
-#endif
-
-         s->state.activity = k_skate_activity_air;
-      }
-      return;
-   }
-
-   v2f steer = { player->input_js1h->axis.value,
-                 player->input_js1v->axis.value };
-   v2_normalize_clamp( steer );
-
-   s->state.steery -= steer[0] * k_steer_air * k_rb_delta;
-   s->state.steerx += steer[1] * s->state.reverse * k_steer_air * k_rb_delta;
-   
-#if 0
-   v4f rotate;
-   q_axis_angle( rotate, player->rb.to_world[0], siX );
-   q_mul( rotate, player.rb.q, player.rb.q );
-#endif
-
-   s->state.slip = 0.0f;
-   s->state.activity = k_skate_activity_grind;
-
-   /* TODO: Compression */
-   v3f up = { 0.0f, 1.0f, 0.0f };
-   float angle = v3_dot( player->rb.to_world[1], up );
-
-   if( fabsf(angle) < 0.99f )
-   {
-      v3f axis; 
-      v3_cross( player->rb.to_world[1], up, axis );
-
-      v4f correction;
-      q_axis_angle( correction, axis, k_rb_delta * 10.0f * acosf(angle) );
-      q_mul( correction, player->rb.q, player->rb.q );
-   }
-
-   float const DOWNFORCE = -k_downforce*1.2f*VG_TIMESTEP_FIXED;
-   v3_muladds( player->rb.v, manifold->n, DOWNFORCE, player->rb.v );
-   m3x3_identity( s->state.velocity_bias );
-   m3x3_identity( s->state.velocity_bias_pstep );
-
-   if( s->state.activity_prev != k_skate_activity_grind )
-   {
-      /* FIXME: Queue audio events instead */
-#if 0
-      audio_lock();
-      audio_player_set_flags( &audio_player_extra, 
-                              AUDIO_FLAG_SPACIAL_3D );
-      audio_player_set_position( &audio_player_extra, player.rb.co );
-      audio_player_set_vol( &audio_player_extra, 20.0f );
-      audio_player_playclip( &audio_player_extra, &audio_board[5] );
-      audio_unlock();
-#endif
-   }
-}
-
-/*
- * Air control, no real physics
- */
-VG_STATIC void skate_apply_air_model( player_interface *player,
-                                      struct player_device_skate *s )
-{
-   if( s->state.activity != k_skate_activity_air )
-      return;
-
-   if( s->state.activity_prev != k_skate_activity_air )
-      player_approximate_best_trajectory( player, s );
-
-   m3x3_mulv( s->state.velocity_bias, player->rb.v, player->rb.v );
-   ray_hit hit;
-
-   /* 
-    * Prediction 
-    */
-   float pstep  = VG_TIMESTEP_FIXED * 1.0f;
-   float k_bias = 0.98f;
-
-   v3f pco, pco1, pv;
-   v3_copy( player->rb.co, pco );
-   v3_muls( player->rb.v, 1.0f, pv );
-   
-   float time_to_impact = 0.0f;
-   float limiter = 1.0f;
-
-   struct grind_edge *best_grind = NULL;
-   float closest_grind = INFINITY;
-
-   v3f target_normal = { 0.0f, 1.0f, 0.0f };
-   int has_target = 0;
-
-   for( int i=0; i<250; i++ )
-   {
-      v3_copy( pco, pco1 );
-      m3x3_mulv( s->state.velocity_bias, pv, pv );
-
-      pv[1] += -k_gravity * pstep;
-      v3_muladds( pco, pv, pstep, pco );
-      
-      ray_hit contact;
-      v3f vdir;
-
-      v3_sub( pco, pco1, vdir );
-      contact.dist = v3_length( vdir );
-      v3_divs( vdir, contact.dist, vdir);
-
-      v3f c0, c1;
-      struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
-                                                        c0, c1, 0.4f );
-
-      if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
-      {
-         vg_line( ge->p0, ge->p1, 0xff0000ff );
-         vg_line_cross( pco, 0xff0000ff, 0.25f );
-         has_target = 1;
-         break;
-      }
-      
-      float orig_dist = contact.dist;
-      if( ray_world( pco1, vdir, &contact ) )
-      {
-         v3_copy( contact.normal, target_normal );
-         has_target = 1;
-         time_to_impact += (contact.dist/orig_dist)*pstep;
-         vg_line_cross( contact.pos, 0xffff0000, 0.25f );
-         break;
-      }
-      time_to_impact += pstep;
-   }
-
-   if( has_target )
-   {
-      float angle = v3_dot( player->rb.to_world[1], target_normal );
-      v3f axis; 
-      v3_cross( player->rb.to_world[1], target_normal, axis );
-
-      limiter = vg_minf( 5.0f, time_to_impact )/5.0f;
-      limiter = 1.0f-limiter;
-      limiter *= limiter;
-      limiter = 1.0f-limiter;
-
-      if( fabsf(angle) < 0.99f )
-      {
-         v4f correction;
-         q_axis_angle( correction, axis, 
-                        acosf(angle)*(1.0f-limiter)*2.0f*VG_TIMESTEP_FIXED );
-         q_mul( correction, player->rb.q, player->rb.q );
-      }
-   }
-
-   v2f steer = { player->input_js1h->axis.value,
-                 player->input_js1v->axis.value };
-   v2_normalize_clamp( steer );
-
-   s->state.steery -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
-   s->state.steerx += steer[1] * s->state.reverse * k_steer_air 
-                                                         * limiter * k_rb_delta;
-   s->land_dist = time_to_impact;
-   v3_copy( target_normal, s->land_normal );
-}
-
-VG_STATIC void skate_get_board_points( player_interface *player,
-                                       struct player_device_skate *s,
-                                       v3f front, v3f back )
-{
-   v3f pos_front = {0.0f,0.0f,-k_board_length},
-       pos_back  = {0.0f,0.0f, k_board_length};
-
-   m4x3_mulv( player->rb.to_world, pos_front, front );
-   m4x3_mulv( player->rb.to_world, pos_back,  back );
-}
-
-/*
- * Casts and pushes a sphere-spring model into the world
- */
-VG_STATIC int skate_simulate_spring( player_interface *player,
-                                     struct player_device_skate *s,
-                                     v3f pos )
-{
-   float mod      = 0.7f * player->input_grab->axis.value + 0.3f,
-         spring_k = mod * k_spring_force,
-         damp_k   = mod * k_spring_dampener,
-         disp_k   = 0.4f;
-
-   v3f start, end;
-   v3_copy( pos, start );
-   v3_muladds( pos, player->rb.to_world[1], -disp_k, end );
-
-   float t;
-   v3f n;
-   int hit_info = spherecast_world( start, end, 0.2f, &t, n );
-
-   if( hit_info != -1 )
-   {
-      v3f F, delta;
-      v3_sub( start, player->rb.co, delta );
-      
-      float displacement = vg_clampf( 1.0f-t, 0.0f, 1.0f ),
-            damp         = 
-         vg_maxf( 0.0f, v3_dot( player->rb.to_world[1], player->rb.v ) );
-
-      v3_muls( player->rb.to_world[1], displacement*spring_k*k_rb_delta -
-                                       damp*damp_k*k_rb_delta, F );
-
-      v3_muladds( player->rb.v, F, 1.0f, player->rb.v );
-      
-      /* Angular velocity */
-      v3f wa;
-      v3_cross( delta, F, wa );
-      v3_muladds( player->rb.w, wa, k_spring_angular, player->rb.w );
-
-      v3_lerp( start, end, t, pos );
-      return 1;
-   }
-   else
-   {
-      v3_copy( end, pos );
-      return 0;
-   }
-}
-
-
-/* 
- * Handles connection between the player and the ground
- */
-VG_STATIC void skate_apply_interface_model( player_interface *player,
-                                            struct player_device_skate *s,
-                                            rb_ct *manifold, int len )
-{
-   if( !((s->state.activity == k_skate_activity_ground) ||
-         (s->state.activity == k_skate_activity_air )) )
-      return;
-
-   if( s->state.activity == k_skate_activity_air )
-      s->debug_normal_pressure = 0.0f;
-   else
-      s->debug_normal_pressure = v3_dot( player->rb.to_world[1], player->rb.v );
-
-   /* springs */
-   v3f spring0, spring1;
-
-   skate_get_board_points( player, s, spring1, spring0 );
-   int spring_hit0 = 0, //skate_simulate_spring( player, s, spring0 ),
-       spring_hit1 = 0; //skate_simulate_spring( player, s, spring1 );
-
-   v3f animavg, animdelta;
-   v3_add( spring0, spring1, animavg );
-   v3_muls( animavg, 0.5f, animavg );
-
-   v3_sub( spring1, spring0, animdelta );
-   v3_normalize( animdelta );
-
-   m4x3_mulv( player->rb.to_local, animavg, s->board_offset );
-
-   float dx = -v3_dot( animdelta, player->rb.to_world[2] ),
-         dy =  v3_dot( animdelta, player->rb.to_world[1] );
-
-   float angle = -atan2f( dy, dx );
-   q_axis_angle( s->board_rotation, (v3f){1.0f,0.0f,0.0f}, angle );
-
-   int lift_frames_limit = 6;
-
-   /* Surface connection */
-   if( len == 0 && !(spring_hit0 && spring_hit1) )
-   {
-      s->state.lift_frames ++;
-
-      if( s->state.lift_frames >= lift_frames_limit )
-         s->state.activity = k_skate_activity_air;
-   }
-   else
-   {
-      v3f surface_avg;
-      v3_zero( surface_avg );
-
-      for( int i=0; i<len; i++ )
-         v3_add( surface_avg, manifold[i].n, surface_avg );
-      v3_normalize( surface_avg );
-      
-      if( v3_dot( player->rb.v, surface_avg ) > 0.7f )
-      {
-         s->state.lift_frames ++;
-
-         if( s->state.lift_frames >= lift_frames_limit )
-            s->state.activity = k_skate_activity_air;
-      }
-      else
-      {
-         s->state.activity = k_skate_activity_ground;
-         s->state.lift_frames = 0;
-         v3f projected, axis;
-
-         float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
-         v3_muladds( player->rb.v, player->rb.to_world[1], 
-                     DOWNFORCE, player->rb.v );
-
-         float d = v3_dot( player->rb.to_world[2], surface_avg );
-         v3_muladds( surface_avg, player->rb.to_world[2], -d, projected );
-         v3_normalize( projected );
-
-         float angle = v3_dot( player->rb.to_world[1], projected );
-         v3_cross( player->rb.to_world[1], projected, axis );
-
-         if( fabsf(angle) < 0.9999f )
-         {
-            v4f correction;
-            q_axis_angle( correction, axis, 
-                          acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
-            q_mul( correction, player->rb.q, player->rb.q );
-         }
-      }
-   }
-}
-
-VG_STATIC void skate_apply_grab_model( player_interface *player,
-                                       struct player_device_skate *s )
-{
-   float grabt = player->input_grab->axis.value;
-
-   if( grabt > 0.5f )
-   {
-      v2_muladds( s->state.grab_mouse_delta, vg.mouse_delta, 0.02f, 
-                  s->state.grab_mouse_delta );
-
-      v2_normalize_clamp( s->state.grab_mouse_delta );
-   }
-   else
-      v2_zero( s->state.grab_mouse_delta );
-
-   s->state.grabbing = vg_lerpf( s->state.grabbing, grabt, 8.4f*k_rb_delta );
-}
-
-/*
- * Computes friction and surface interface model
- */
-VG_STATIC void skate_apply_friction_model( player_interface *player,
-                                           struct player_device_skate *s )
-{
-   if( s->state.activity != k_skate_activity_ground )
-      return;
-
-   /*
-    * Computing localized friction forces for controlling the character
-    * Friction across X is significantly more than Z
-    */
-
-   v3f vel;
-   m3x3_mulv( player->rb.to_local, player->rb.v, vel );
-   float slip = 0.0f;
-   
-   if( fabsf(vel[2]) > 0.01f )
-      slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
-
-   if( fabsf( slip ) > 1.2f )
-      slip = vg_signf( slip ) * 1.2f;
-
-   s->state.slip = slip;
-   s->state.reverse = -vg_signf(vel[2]);
-
-   vel[0] += vg_cfrictf( vel[0], k_friction_lat * k_rb_delta );
-   vel[2] += vg_cfrictf( vel[2], k_friction_resistance * k_rb_delta );
-
-   /* Pushing additive force */
-
-   if( !player->input_jump->button.value )
-   {
-      if( player->input_push->button.value )
-      {
-         if( (vg.time - s->state.cur_push) > 0.25 )
-            s->state.start_push = vg.time;
-
-         s->state.cur_push = vg.time;
-
-         double push_time = vg.time - s->state.start_push;
-
-         float cycle_time = push_time*k_push_cycle_rate,
-               accel      = k_push_accel * (sinf(cycle_time)*0.5f+0.5f),
-               amt        = accel * VG_TIMESTEP_FIXED,
-               current    = v3_length( vel ),
-               new_vel    = vg_minf( current + amt, k_max_push_speed ),
-               delta      = new_vel - vg_minf( current, k_max_push_speed );
-
-         vel[2] += delta * -s->state.reverse;
-      }
-   }
-
-   /* Send back to velocity */
-   m3x3_mulv( player->rb.to_world, vel, player->rb.v );
-   
-   /* Steering */
-   float input = player->input_js1h->axis.value,
-         grab  = player->input_grab->axis.value,
-         steer = input * (1.0f-(s->state.jump_charge+grab)*0.4f),
-         steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
-
-   s->state.steery -= steer_scaled * k_rb_delta;
-}
-
-VG_STATIC void skate_apply_jump_model( player_interface *player,
-                                       struct player_device_skate *s )
-{
-   int charging_jump_prev = s->state.charging_jump;
-   s->state.charging_jump = player->input_jump->button.value;
-
-   /* Cannot charge this in air */
-   if( s->state.activity != k_skate_activity_ground )
-      s->state.charging_jump = 0;
-
-   if( s->state.charging_jump )
-   {
-      s->state.jump_charge += k_rb_delta * k_jump_charge_speed;
-
-      if( !charging_jump_prev )
-         s->state.jump_dir = s->state.reverse>0.0f? 1: 0;
-   }
-   else
-   {
-      s->state.jump_charge -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
-   }
-
-   s->state.jump_charge = vg_clampf( s->state.jump_charge, 0.0f, 1.0f );
-
-   if( s->state.activity == k_skate_activity_air )
-      return;
-
-   /* player let go after charging past 0.2: trigger jump */
-   if( (!s->state.charging_jump) && (s->state.jump_charge > 0.2f) )
-   {
-      v3f jumpdir;
-      
-      /* Launch more up if alignment is up else improve velocity */
-      float aup = v3_dot( (v3f){0.0f,1.0f,0.0f}, player->rb.to_world[1] ),
-            mod = 0.5f,
-            dir = mod + fabsf(aup)*(1.0f-mod);
-
-      v3_copy( player->rb.v, jumpdir );
-      v3_normalize( jumpdir );
-      v3_muls( jumpdir, 1.0f-dir, jumpdir );
-      v3_muladds( jumpdir, player->rb.to_world[1], dir, jumpdir );
-      v3_normalize( jumpdir );
-      
-      float force = k_jump_force*s->state.jump_charge;
-      v3_muladds( player->rb.v, jumpdir, force, player->rb.v );
-      s->state.jump_charge = 0.0f;
-
-      s->state.jump_time = vg.time;
-
-      v2f steer = { player->input_js1h->axis.value,
-                    player->input_js1v->axis.value };
-      v2_normalize_clamp( steer );
-
-      float maxspin = k_steer_air * k_rb_delta * k_spin_boost;
-      s->state.steery_s = -steer[0] * maxspin;
-      s->state.steerx = s->state.steerx_s;
-
-      /* FIXME audio events */
-#if 0
-      audio_lock();
-      audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
-      audio_player_set_position( &audio_player_extra, player.rb.co );
-      audio_player_set_vol( &audio_player_extra, 20.0f );
-      audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
-      audio_unlock();
-#endif
-   }
-}
-
-VG_STATIC void skate_apply_pump_model( player_interface *player,
-                                       struct player_device_skate *s )
-{
-   /* Throw / collect routine 
-    *
-    * TODO: Max speed boost
-    */
-   if( player->input_grab->axis.value > 0.5f )
-   {
-      if( s->state.activity == k_skate_activity_ground )
-      {
-         /* Throw */
-         v3_muls( player->rb.to_world[1], k_mmthrow_scale, s->state.throw_v );
-      }
-   }
-   else
-   {
-      /* Collect */
-      float doty = v3_dot( player->rb.to_world[1], s->state.throw_v );
-      
-      v3f Fl, Fv;
-      v3_muladds( s->state.throw_v, player->rb.to_world[1], -doty, Fl);
-
-      if( s->state.activity == k_skate_activity_ground )
-      {
-         v3_muladds( player->rb.v,     Fl,  k_mmcollect_lat, player->rb.v );
-         v3_muladds( s->state.throw_v, Fl, -k_mmcollect_lat, s->state.throw_v );
-      }
-
-      v3_muls( player->rb.to_world[1], -doty, Fv );
-      v3_muladds( player->rb.v,     Fv, k_mmcollect_vert, player->rb.v );
-      v3_muladds( s->state.throw_v, Fv, k_mmcollect_vert, s->state.throw_v );
-   }
-
-   /* Decay */
-   if( v3_length2( s->state.throw_v ) > 0.0001f )
-   {
-      v3f dir;
-      v3_copy( s->state.throw_v, dir );
-      v3_normalize( dir );
-
-      float max = v3_dot( dir, s->state.throw_v ),
-            amt = vg_minf( k_mmdecay * k_rb_delta, max );
-      v3_muladds( s->state.throw_v, dir, -amt, s->state.throw_v );
-   }
-}
-
-VG_STATIC void skate_apply_cog_model( player_interface *player,
-                                      struct player_device_skate *s )
-{
-   v3f ideal_cog, ideal_diff;
-   v3_muladds( player->rb.co, player->rb.to_world[1],
-               1.0f-player->input_grab->axis.value, ideal_cog );
-   v3_sub( ideal_cog, s->state.cog, ideal_diff );
-
-   /* Apply velocities */
-   v3f rv;
-   v3_sub( player->rb.v, s->state.cog_v, rv );
-
-   v3f F;
-   v3_muls( ideal_diff, -k_cog_spring * k_rb_rate, F );
-   v3_muladds( F, rv,   -k_cog_damp * k_rb_rate, F );
-
-   float ra = k_cog_mass_ratio,
-         rb = 1.0f-k_cog_mass_ratio;
-
-   /* Apply forces & intergrate */
-   v3_muladds( s->state.cog_v, F, -rb, s->state.cog_v );
-   s->state.cog_v[1] += -9.8f * k_rb_delta;
-   v3_muladds( s->state.cog, s->state.cog_v, k_rb_delta, s->state.cog );
-}
-
-VG_STATIC void skate_collision_response( player_interface *player,
-                                         struct player_device_skate *s,
-                                         rb_ct *manifold, int len )
-{
-   for( int j=0; j<10; j++ )
-   {
-      for( int i=0; i<len; i++ )
-      {
-         struct contact *ct = &manifold[i];
-         
-         v3f dv, delta;
-         v3_sub( ct->co, player->rb.co, delta ); 
-         v3_cross( player->rb.w, delta, dv );
-         v3_add( player->rb.v, dv, dv );
-
-         float vn = -v3_dot( dv, ct->n );
-         vn += ct->bias;
-
-         float temp = ct->norm_impulse;
-         ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
-         vn = ct->norm_impulse - temp;
-
-         v3f impulse;
-         v3_muls( ct->n, vn, impulse );
-
-         if( fabsf(v3_dot( impulse, player->rb.to_world[2] )) > 10.0f ||
-             fabsf(v3_dot( impulse, player->rb.to_world[1] )) > 50.0f )
-         {
-            /* FIXME */
-#if 0
-            player_kill();
-            return;
-#endif
-         }
-
-         v3_add( impulse, player->rb.v, player->rb.v );
-         v3_cross( delta, impulse, impulse );
-
-         /*
-          * W Impulses are limited to the Y and X axises, we don't really want
-          * roll angular velocities being included.
-          *
-          * Can also tweak the resistance of each axis here by scaling the wx,wy
-          * components.
-          */
-         
-         float wy = v3_dot( player->rb.to_world[1], impulse ) * 0.8f,
-               wx = v3_dot( player->rb.to_world[0], impulse ) * 1.0f;
-
-         v3_muladds( player->rb.w, player->rb.to_world[1], wy, player->rb.w );
-         v3_muladds( player->rb.w, player->rb.to_world[0], wx, player->rb.w );
-      }
-   }
-}
-
-VG_STATIC void skate_integrate( player_interface *player,
-                                struct player_device_skate *s )
-{
-   /* integrate rigidbody velocities */
-   v3f gravity = { 0.0f, -9.6f, 0.0f };
-   v3_muladds( player->rb.v, gravity, k_rb_delta, player->rb.v );
-   v3_muladds( player->rb.co, player->rb.v, k_rb_delta, player->rb.co );
-
-   float decay_rate = 0.5f*0.125f;
-
-   if( s->state.activity == k_skate_activity_air )
-   {
-      float dist = 1.0f-(s->land_dist/4.0f);
-      decay_rate = 0.5f * vg_maxf( dist*dist, 0.0f );
-   }
-
-   v3_lerp( player->rb.w, (v3f){0.0f,0.0f,0.0f}, decay_rate, player->rb.w );
-
-   if( v3_length2( player->rb.w ) > 0.0f )
-   {
-      v4f rotation;
-      v3f axis;
-      v3_copy( player->rb.w, axis );
-      
-      float mag = v3_length( axis );
-      v3_divs( axis, mag, axis );
-      q_axis_angle( rotation, axis, mag*k_rb_delta );
-      q_mul( rotation, player->rb.q, player->rb.q );
-   }
-
-   /* integrate steering velocities */
-   v4f rotate; 
-   float l = (s->state.activity == k_skate_activity_air)? 0.04f: 0.24f;
-
-   s->state.steery_s = vg_lerpf( s->state.steery_s, s->state.steery, l );
-   s->state.steerx_s = vg_lerpf( s->state.steerx_s, s->state.steerx, l );
-
-   q_axis_angle( rotate, player->rb.to_world[1], s->state.steery_s );
-   q_mul( rotate, player->rb.q, player->rb.q );
-
-   q_axis_angle( rotate, player->rb.to_world[0], s->state.steerx_s );
-   q_mul( rotate, player->rb.q, player->rb.q );
-
-   s->state.steerx = 0.0f;
-   s->state.steery = 0.0f;
-
-#if 0
-   v3_sub( player.rb.v, s->phys.v_prev, s->phys.a );
-   v3_muls( s->phys.a, 1.0f/VG_TIMESTEP_FIXED, s->phys.a );
-   v3_copy( player.rb.v, s->phys.v_prev );
-#endif
-
-   s->state.flip_time += s->state.flip_rate * k_rb_delta;
-   rb_update_transform( &player->rb );
-}
-
-VG_STATIC void player_skate_update( player_device *dev,
-                                    player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   v3_copy( player->rb.co, s->state.prev_pos );
-   s->state.activity_prev = s->state.activity;
-
-   /* Setup colliders */
-   m4x3f mtx_front, mtx_back;
-   m3x3_identity( mtx_front );
-   m3x3_identity( mtx_back );
-
-   skate_get_board_points( player, s, mtx_front[3], mtx_back[3] );
-
-   s->sphere_back.radius = 0.3f;
-   s->sphere_front.radius = 0.3f;
-
-   /* create manifold(s) */
-   rb_ct manifold[72],
-         *interface_manifold = NULL,
-         *grind_manifold = NULL;
-
-   int 
-   len_front = skate_collide_smooth( player, mtx_front, 
-                                     &s->sphere_front, manifold ),
-   len_back = skate_collide_smooth( player, mtx_back,  
-                                    &s->sphere_back, &manifold[len_front] ),
-   interface_len = len_front + len_back;
-
-   /* try to slap both wheels onto the ground when landing to prevent mega 
-    * angular velocities being added */
-   if( (s->state.activity == k_skate_activity_air) && (len_front != len_back) )
-   {
-      v3f trace_from, trace_dir;
-      v3_muls( player->rb.to_world[1], -1.0f, trace_dir );
-
-      if( len_front )
-         v3_copy( mtx_back[3],  trace_from );
-      else
-         v3_copy( mtx_front[3], trace_from );
-
-      ray_hit ray;
-      ray.dist = 0.6f;
-
-      if( ray_world( trace_from, trace_dir, &ray ) )
-      {
-         rb_ct *ct = &manifold[ interface_len ];
-
-         v3_copy( ray.pos, ct->co );
-         v3_copy( ray.normal, ct->n );
-         ct->p = 0.0f;
-
-         interface_len ++;
-      }
-   }
-
-   interface_manifold = manifold;
-   grind_manifold = manifold + interface_len;
-
-   int grind_len = skate_grind_collide( dev, player, grind_manifold );
-
-   for( int i=0; i<interface_len+grind_len; i ++ )
-   {
-      rb_prepare_contact( &manifold[i] );
-      rb_debug_contact( &manifold[i] );
-   }
-
-   skate_apply_grind_model( player, s, grind_manifold, grind_len );
-   skate_apply_interface_model( player, s, manifold, interface_len );
-   
-   skate_apply_pump_model( player, s );
-   skate_apply_cog_model( player, s );
-   skate_collision_response( player, s, manifold, interface_len + grind_len );
-
-   skate_apply_grab_model( player, s );
-   skate_apply_friction_model( player, s );
-   skate_apply_jump_model( player, s );
-   skate_apply_air_model( player, s );
-
-   skate_integrate( player, s );
-
-   vg_line_pt3( s->state.cog, 0.1f,  VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.11f, VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.12f, VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.13f, VG__WHITE );
-   vg_line_pt3( s->state.cog, 0.14f, VG__WHITE );
-
-   vg_line( player->rb.co, s->state.cog, VG__RED );
-
-
-   teleport_gate *gate;
-   if( (gate = world_intersect_gates( player->rb.co, s->state.prev_pos )) )
-   {
-      m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
-      m3x3_mulv( gate->transport, player->rb.v,  player->rb.v );
-      m4x3_mulv( gate->transport, s->state.cog,   s->state.cog );
-      m3x3_mulv( gate->transport, s->state.cog_v, s->state.cog_v );
-      m3x3_mulv( gate->transport, s->state.throw_v, s->state.throw_v );
-
-      /*camera */
-      m4x3_mulv( gate->transport, s->state.posl, s->state.posl );
-      m3x3_mulv( gate->transport, s->state.vl, s->state.vl );
-      m3x3_mulv( gate->transport, s->state.dirl, s->state.dirl );
-
-#if 0
-      mixedcam_transport( &s->state.cam, gate );
-#endif
-
-      v4f transport_rotation;
-      m3x3_q( gate->transport, transport_rotation );
-      q_mul( transport_rotation, player->rb.q, player->rb.q );
-      rb_update_transform( &player->rb );
-
-      s->state_gate_storage = s->state;
-      player_pass_gate( player, gate );
-   }
-}
-
-VG_STATIC void player_skate_ui( player_device *dev, player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-
-   /* FIXME: Compression */
-   player_debugtext( 1, "V:  %5.2f %5.2f %5.2f",player->rb.v[0],
-                                                player->rb.v[1],
-                                                player->rb.v[2] );
-   player_debugtext( 1, "CO: %5.2f %5.2f %5.2f",player->rb.co[0],
-                                                player->rb.co[1],
-                                                player->rb.co[2] );
-   player_debugtext( 1, "W:  %5.2f %5.2f %5.2f",player->rb.w[0],
-                                                player->rb.w[1],
-                                                player->rb.w[2] );
-
-   player_debugtext( 1, "activity: %s\n",
-                           (const char *[]){ "k_skate_activity_air",
-                                             "k_skate_activity_ground",
-                                             "k_skate_activity_grind }" }
-                                             [s->state.activity] );
-   player_debugtext( 1, "steer_s: %5.2f %5.2f [%.2f %.2f]\n",
-                        s->state.steerx_s, s->state.steery_s,
-                        k_steer_ground, k_steer_air );
-   player_debugtext( 1, "flip: %.4f %.4f\n", s->state.flip_rate, 
-                                             s->state.flip_time );
-}
-
-VG_STATIC void player_skate_animate( player_device *dev, 
-                                     player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-   struct skeleton *sk = &av->sk;
-
-   /* Camera position */
-   /* TODO split up */
-   /* FIXME */
-
-#if 0
-   v3_muladds( phys->m, phys->a, VG_TIMESTEP_FIXED, phys->m );
-   v3_lerp( phys->m, (v3f){0.0f,0.0f,0.0f}, 0.1f, phys->m );
-
-   phys->m[0] = vg_clampf( phys->m[0], -2.0f, 2.0f );
-   phys->m[1] = vg_clampf( phys->m[1], -2.0f, 2.0f );
-   phys->m[2] = vg_clampf( phys->m[2], -2.0f, 2.0f );
-   v3_lerp( phys->bob, phys->m, 0.2f, phys->bob );
-#endif
-
-   /* Head */
-   float kheight = 2.0f,
-         kleg = 0.6f;
-
-   v3f offset;
-   v3_zero( offset );
-
-   m4x3_mulv( player->rb.to_local, s->state.cog, offset );
-   v3_muls( offset, -4.0f, offset );
-
-#if 0
-   m3x3_mulv( player.inv_visual_transform, phys->bob, offset );
-#endif
-
-   static float speed_wobble = 0.0f, speed_wobble_2 = 0.0f;
-
-   float curspeed  = v3_length( player->rb.v ),
-         kickspeed = vg_clampf( curspeed*(1.0f/40.0f), 0.0f, 1.0f ),
-         kicks     = (vg_randf()-0.5f)*2.0f*kickspeed,
-         sign      = vg_signf( kicks );
-
-   s->wobble[0] = vg_lerpf( s->wobble[0], kicks*kicks*sign, 6.0f*vg.time_delta);
-   s->wobble[1] = vg_lerpf( s->wobble[1], speed_wobble,     2.4f*vg.time_delta);
-
-   offset[0] *= 0.26f;
-   offset[0] += speed_wobble_2*3.0f;
-
-   offset[1] *= -0.3f;
-   offset[2] *= 0.01f;
-
-   offset[0]=vg_clampf(offset[0],-0.8f,0.8f)*(1.0f-fabsf(s->blend_slide)*0.9f);
-   offset[1]=vg_clampf(offset[1],-0.5f,0.0f);
-
-   /* 
-    * Animation blending
-    * ===========================================
-    */
-   
-   /* sliding */
-   {
-      float desired  = vg_clampf( fabsf( s->state.slip ), 0.0f, 1.0f );
-      s->blend_slide = vg_lerpf( s->blend_slide, desired, 2.4f*vg.time_delta);
-   }
-   
-   /* movement information */
-   {
-      int iair = (s->state.activity == k_skate_activity_air) ||
-                 (s->state.activity == k_skate_activity_grind );
-
-      float dirz = s->state.reverse > 0.0f? 0.0f: 1.0f,
-            dirx = s->state.slip < 0.0f?    0.0f: 1.0f,
-            fly  = iair?                    1.0f: 0.0f;
-
-      s->blend_z    = vg_lerpf( s->blend_z,   dirz, 2.4f*vg.time_delta );
-      s->blend_x    = vg_lerpf( s->blend_x,   dirx, 0.6f*vg.time_delta );
-      s->blend_fly  = vg_lerpf( s->blend_fly, fly,  2.4f*vg.time_delta );
-   }
-
-   mdl_keyframe apose[32], bpose[32];
-   mdl_keyframe ground_pose[32];
-   {
-      /* when the player is moving fast he will crouch down a little bit */
-      float stand = 1.0f - vg_clampf( curspeed * 0.03f, 0.0f, 1.0f );
-      s->blend_stand = vg_lerpf( s->blend_stand, stand, 6.0f*vg.time_delta );
-
-      /* stand/crouch */
-      float dir_frame   = s->blend_z * (15.0f/30.0f),
-            stand_blend = offset[1]*-2.0f;
-
-      v3f local_cog;
-      m4x3_mulv( player->rb.to_local, s->state.cog, local_cog );
-
-      stand_blend = vg_clampf( 1.0f-local_cog[1], 0, 1 );
-
-      skeleton_sample_anim( sk, s->anim_stand, dir_frame, apose );
-      skeleton_sample_anim( sk, s->anim_highg, dir_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
-
-      /* sliding */
-      float slide_frame = s->blend_x * (15.0f/30.0f);
-      skeleton_sample_anim( sk, s->anim_slide, slide_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, s->blend_slide, apose );
-
-      /* pushing */
-      double push_time = vg.time - s->state.start_push;
-      s->blend_push = vg_lerpf( s->blend_push,
-                               (vg.time - s->state.cur_push) < 0.125,
-                               6.0f*vg.time_delta );
-
-      float pt = push_time + vg.accumulator;
-      if( s->state.reverse > 0.0f )
-         skeleton_sample_anim( sk, s->anim_push, pt, bpose );
-      else
-         skeleton_sample_anim( sk, s->anim_push_reverse, pt, bpose );
-
-      skeleton_lerp_pose( sk, apose, bpose, s->blend_push, apose );
-
-      /* trick setup */
-      float jump_start_frame = 14.0f/30.0f;
-
-      float charge = s->state.jump_charge;
-      s->blend_jump = vg_lerpf( s->blend_jump, charge, 8.4f*vg.time_delta );
-
-      float setup_frame = charge * jump_start_frame,
-            setup_blend = vg_minf( s->blend_jump, 1.0f );
-      
-      float jump_frame = (vg.time - s->state.jump_time) + jump_start_frame;
-      if( jump_frame >= jump_start_frame && jump_frame <= (40.0f/30.0f) )
-         setup_frame = jump_frame;
-
-      struct skeleton_anim *jump_anim = s->state.jump_dir?
-                                        s->anim_ollie:
-                                        s->anim_ollie_reverse;
-
-      skeleton_sample_anim_clamped( sk, jump_anim, setup_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
-   }
-   
-   mdl_keyframe air_pose[32];
-   {
-      float target = -player->input_js1h->axis.value;
-      s->blend_airdir = vg_lerpf( s->blend_airdir, target, 2.4f*vg.time_delta );
-      
-      float air_frame = (s->blend_airdir*0.5f+0.5f) * (15.0f/30.0f);
-      skeleton_sample_anim( sk, s->anim_air, air_frame, apose );
-
-      static v2f grab_choice;
-
-      v2f grab_input = { player->input_js2h->axis.value,
-                         player->input_js2v->axis.value };
-      v2_add( s->state.grab_mouse_delta, grab_input, grab_input );
-      if( v2_length2( grab_input ) <= 0.001f )
-         grab_input[0] = -1.0f;
-      else
-         v2_normalize_clamp( grab_input );
-      v2_lerp( grab_choice, grab_input, 2.4f*vg.time_delta, grab_choice );
-
-      float ang = atan2f( grab_choice[0], grab_choice[1] ),
-            ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
-            grab_frame = ang_unit * (15.0f/30.0f);
-
-      skeleton_sample_anim( sk, s->anim_grabs, grab_frame, bpose );
-      skeleton_lerp_pose( sk, apose, bpose, s->state.grabbing, air_pose );
-   }
-
-   skeleton_lerp_pose( sk, ground_pose, air_pose, s->blend_fly, dev->pose );
-
-   float add_grab_mod = 1.0f - s->blend_fly;
-
-   /* additive effects */
-   {
-      u32 apply_to[] = { av->id_hip, 
-                         av->id_ik_hand_l,
-                         av->id_ik_hand_r,
-                         av->id_ik_elbow_l,
-                         av->id_ik_elbow_r };
-
-      for( int i=0; i<vg_list_size(apply_to); i ++ )
-      {
-         dev->pose[apply_to[i]-1].co[0] += offset[0]*add_grab_mod;
-         dev->pose[apply_to[i]-1].co[2] += offset[2]*add_grab_mod;
-      }
-
-      mdl_keyframe *kf_board  = &dev->pose[av->id_board-1],
-                   *kf_foot_l = &dev->pose[av->id_ik_foot_l-1],
-                   *kf_foot_r = &dev->pose[av->id_ik_foot_r-1];
-
-      v3f bo;
-      v3_muls( s->board_offset, add_grab_mod, bo );
-
-      v3_add( bo, kf_board->co,  kf_board->co );
-      v3_add( bo, kf_foot_l->co, kf_foot_l->co );
-      v3_add( bo, kf_foot_r->co, kf_foot_r->co );
-
-      m3x3f c;
-      q_m3x3( s->board_rotation, c );
-
-      v3f d;
-      v3_sub( kf_foot_l->co, bo, d );
-      m3x3_mulv( c, d, d );
-      v3_add( bo, d, kf_foot_l->co );
-      
-      v3_sub( kf_foot_r->co, bo, d );
-      m3x3_mulv( c, d, d );
-      v3_add( bo, d, kf_foot_r->co );
-
-      q_mul( s->board_rotation, kf_board->q, kf_board->q );
-      q_normalize( kf_board->q );
-   }
-
-   /* transform */
-   rb_extrapolate( &player->rb, dev->pose_root_co, dev->pose_root_q );
-
-   v3_muladds( dev->pose_root_co, player->rb.to_world[1], -0.28f, 
-               dev->pose_root_co );
-
-   v4f qresy, qresx, qresidual;
-   m3x3f mtx_residual;
-   float substep = vg_clampf( vg.accumulator / VG_TIMESTEP_FIXED, 0.0f, 1.0f );
-   q_axis_angle( qresy, player->rb.to_world[1], s->state.steery_s*substep );
-   q_axis_angle( qresx, player->rb.to_world[0], s->state.steerx_s*substep );
-
-   q_mul( qresy, qresx, qresidual );
-   q_normalize( qresidual );
-   q_mul( dev->pose_root_q, qresidual, dev->pose_root_q );
-   q_normalize( dev->pose_root_q );
-
-   v4f qflip;
-   if( (s->state.activity == k_skate_activity_air) &&
-       (fabsf(s->state.flip_rate) > 0.01f) )
-   {
-      float angle = vg_clampf( s->state.flip_time, -1.0f, 1.0f ) * VG_TAUf,
-            distm = s->land_dist * fabsf(s->state.flip_rate) * 3.0f,
-            blend = vg_clampf( 1.0f-distm, 0.0f, 1.0f );
-
-      angle = vg_lerpf( angle, vg_signf(s->state.flip_rate) * VG_TAUf, blend );
-
-      q_axis_angle( qflip, s->state.flip_axis, angle );
-      q_mul( qflip, dev->pose_root_q, dev->pose_root_q );
-      q_normalize( dev->pose_root_q );
-
-      v3f rotation_point, rco;
-      v3_muladds( player->rb.co, player->rb.to_world[1], 0.5f, rotation_point );
-      v3_sub( dev->pose_root_co, rotation_point, rco );
-      
-      /* FIXME: q_mul v3 */
-      m3x3f TEMP;
-      q_m3x3( qflip, TEMP );
-      m3x3_mulv( TEMP, rco, rco );
-      v3_add( rco, rotation_point, dev->pose_root_co );
-   }
-
-#if 0
-   if( cl_thirdperson )
-   {
-      if( !followcam_will_hit_gate( player, &s->state.cam ) )
-      {
-         m4x3f inverse;
-         m4x3_invert_affine( s->state.cam.gate->transport, inverse );
-         m4x3_mul( inverse, transform, transform );
-      }
-   }
-#endif
-}
-
-VG_STATIC void skate_camera_vector_look( camera *cam, v3f v, float C, float k )
-{
-   float yaw = atan2f( v[0], -v[2] ),
-       pitch = atan2f
-               ( 
-                   -v[1], 
-                   sqrtf
-                   (
-                     v[0]*v[0] + v[2]*v[2]
-                   )
-               ) * C + k;
-
-   cam->angles[0] = yaw;
-   cam->angles[1] = pitch;
-}
-
-VG_STATIC void skate_camera_firstperson( player_device *dev,
-                                         player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   /* FIXME: viewpoint entity */
-   v3f vp = {-0.1f,1.8f,0.0f};
-   m4x3_mulv( av->sk.final_mtx[ av->id_head-1 ], vp, dev->cam_1st.pos );
-
-   v3_zero( dev->cam_1st.angles );
-   dev->cam_1st.fov = 119.0f;
-
-   v3f flat_dir,
-       vel_dir,
-       look_dir;
-
-   v3_copy( player->rb.v, vel_dir );
-   //v3_normalize( vel_dir );
-
-   float tti = s->land_dist;
-   v3f   norm;
-   v3_copy( s->land_normal, norm );
-
-   if( s->state.activity == k_skate_activity_ground )
-   {
-      tti = 0.0f;
-      v3_copy( player->rb.to_world[1], norm );
-   }
-
-   v3_muladds( vel_dir, norm, -v3_dot(vel_dir,norm), flat_dir );
-   //v3_normalize( flat_dir );
-
-   v3_lerp( flat_dir, vel_dir, vg_clampf( tti / 2.0f, 0.4f, 1.0f ), look_dir );
-   v3_lerp( s->state.vl, look_dir, 4.0f*vg.time_delta, s->state.vl );
-
-   skate_camera_vector_look( &dev->cam_1st, s->state.vl, 1.0f, 0.25f );
-}
-
-VG_STATIC void skate_camera_thirdperson( player_device *dev,
-                                         player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   v3f origin, dir, target;
-   v3_copy( player->rb.co, origin );
-   v3_add( origin, (v3f){0.0f,1.35f,0.0f}, origin );
-   v3_sub( origin, s->state.posl, dir );
-   
-   if( v3_length2( dir ) < 0.1f*0.1f )
-      v3_copy( (v3f){ 0.0f, 0.0f, 1.0f }, dir );   /* FIXME */
-   else
-      v3_normalize( dir );
-
-   if( s->state.activity == k_skate_activity_air )
-      dir[1] *= vg_maxf( 0.0f, 1.0f - (s->land_dist/2.0f) );
-   dir[1] *= 0.0f;
-
-   v3_muladds( origin, dir, -2.0f, target );
-
-   v3_lerp( s->state.posl, target, vg.frame_delta * 15.0f, s->state.posl );
-   v3_lerp( s->state.dirl, dir, 18.0f*vg.time_delta, s->state.dirl );
-
-   v3_copy( s->state.posl, dev->cam_3rd.pos );
-   skate_camera_vector_look( &dev->cam_3rd, s->state.dirl, 1.0f, 0.2f );
-}
-
-VG_STATIC void player_skate_post_animate( player_device *dev,
-                                          player_interface *player )
-{
-   struct player_device_skate *s = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   v3_zero( dev->cam_1st.pos );
-   v3_zero( dev->cam_1st.angles );
-   dev->cam_1st.fov = 90.0f;
-
-   skate_camera_thirdperson( dev, player );
-   skate_camera_firstperson( dev, player );
-
-   /* FIXME: Organize this. Its int wrong fucking place */
-   v3f vp0 = {0.0f,0.1f, 0.6f},
-       vp1 = {0.0f,0.1f,-0.6f};
-
-   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp0, TEMP_BOARD_0 );
-   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp1, TEMP_BOARD_1 );
-}
-
-VG_STATIC void player_skate_reset( player_device *dev,
-                                   player_interface *player,
-                                   struct respawn_point *rp )
-{
-   struct player_device_skate *s = dev->storage;
-   v3_muladds( player->rb.co, player->rb.to_world[1], 1.0f, s->state.cog );
-
-#if 0
-   mixedcam_reset( player, &s->state.cam );
-#endif
-}
-
-VG_STATIC int player_skate_event( player_device *dev, player_interface *player,
-                                  enum player_device_event_type ev, 
-                                  void *data )
-{
-   struct player_device_skate *s = dev->storage;
-
-   if( ev == k_player_device_event_bind )
-      player_skate_bind( dev, player );
-   else if( ev == k_player_device_event_respawn )
-      player_skate_reset( dev, player, data );
-   else if( ev == k_player_device_event_pre_update )
-   {
-      if( vg_input_button_down( player->input_use ) )
-      {
-         struct device_transition_walk inf;
-         v3_copy( player->cam.angles, inf.angles );
-         inf.angles[2] = 0.0f;
-
-         player_transition_to_device( player, s->device_id_walk, &inf );
-         return 1;
-      }
-   }
-   else if( ev == k_player_device_event_custom_transition )
-   {
-      /* transition coming in from walking */
-      struct device_transition_skateboard *inf = data;
-
-      q_axis_angle( player->rb.q, (v3f){0.0f,1.0f,0.0f}, 
-                    atan2f( inf->dir[0], inf->dir[2] ) );
-      v3_copy( player->cam.pos, s->state.posl );
-
-      m3x3f temp;
-      euler_m3x3( player->cam.angles, temp );
-      v3_muls( temp[2], -1.0f, s->state.dirl );
-
-      rb_update_transform( &player->rb );
-      v3_muladds( player->rb.co, player->rb.to_world[1], 1.0f, s->state.cog );
-      v3_copy( player->rb.v, s->state.cog_v );
-   }
-   else if( ev == k_player_device_event_update )
-   {
-      player_skate_update( dev, player );
-   }
-   else if( ev == k_player_device_event_post_update )
-   {
-      for( int i=0; i<s->prediction_count; i++ )
-      {
-         struct land_prediction *p = &s->predictions[i];
-         
-         for( int j=0; j<p->log_length - 1; j ++ )
-            vg_line( p->log[j], p->log[j+1], p->colour );
-
-         vg_line_cross( p->log[p->log_length-1], p->colour, 0.25f );
-
-         v3f p1;
-         v3_add( p->log[p->log_length-1], p->n, p1 );
-         vg_line( p->log[p->log_length-1], p1, 0xffffffff );
-
-         vg_line_pt3( p->apex, 0.02f, 0xffffffff );
-      }
-
-      vg_line_pt3( s->state.apex, 0.200f, 0xff0000ff );
-      vg_line_pt3( s->state.apex, 0.201f, 0xff00ffff );
-   }
-   else if( ev == k_player_device_event_animate )
-   {
-      player_skate_animate( dev, player );
-   }
-   else if( ev == k_player_device_event_post_animate )
-   {
-      player_skate_post_animate( dev, player );
-   }
-   else if( ev == k_player_device_event_debug_ui )
-   {
-      player_skate_ui( dev, player );
-   }
-   else
-      return 0;
-
-   return 1;
-}
-
-VG_STATIC player_device player_device_skate =
-{
-   .name          = "skateboard",
-   .event         = player_skate_event,
-   .storage       = &localplayer_device_skate
-};
-
-#endif /* PLAYER_DEVICE_SKATE_H */
diff --git a/player_device_walk.h b/player_device_walk.h
deleted file mode 100644 (file)
index c1fbd4f..0000000
+++ /dev/null
@@ -1,574 +0,0 @@
-#ifndef PLAYER_DEVICE_WALK_H
-#define PLAYER_DEVICE_WALK_H
-
-#include "player_interface.h"
-#include "player_device_common.h"
-#include "skeleton.h"
-#include "player_model.h"
-
-VG_STATIC
-struct player_device_walk
-{
-   rb_capsule collider;
-
-   struct
-   {
-      v3f angles;
-      v3f prev_pos;
-
-      enum walk_activity
-      {
-         k_walk_activity_air,
-         k_walk_activity_ground,
-         k_walk_activity_sleep
-      }
-      activity;
-   }
-   state,
-   state_gate_storage;
-
-   enum mdl_surface_prop surface;
-   struct skeleton_anim *anim_walk, *anim_run, *anim_idle, *anim_jump;
-
-   float blend_fly,
-         blend_run,
-         blend_walk,
-
-         move_speed,
-         walk_timer;
-
-   u32 device_id_skate;
-}
-localplayer_device_walk;
-
-VG_STATIC void player_walk_pre_update( player_device *dev, 
-                                       player_interface *player )
-{
-   struct player_device_walk *w = dev->storage;
-   player_look( player, w->state.angles );
-
-   if( vg_input_button_down( player->input_use ) )
-   {
-      struct device_transition_skateboard inf;
-
-      v3f xy_speed;
-      v3_copy( player->rb.v, xy_speed );
-      xy_speed[1] = 0.0f;
-
-      if( v3_length2( xy_speed ) > 0.1f * 0.1f )
-      {
-         v3_copy( player->rb.v, inf.dir );
-      }
-      else
-      {
-         inf.dir[0] = -sinf( -w->state.angles[0] );
-         inf.dir[1] =  0.0f;
-         inf.dir[2] = -cosf( -w->state.angles[0] );
-         v3_muls( inf.dir, 1.6f, player->rb.v );
-      }
-
-      player_transition_to_device( player, w->device_id_skate, &inf );
-      return;
-   }
-
-#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_device *dev, 
-                                   player_interface *player )
-{
-   struct player_device_walk *w = dev->storage;
-   v3_copy( player->rb.co, w->state.prev_pos );
-
-   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 );
-
-   w->move_speed = v2_length( 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 )
-   {
-      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 );
-
-   /* 
-    * CCD routine 
-    * ---------------------------------------------------
-    *
-    */
-   v3f lwr_prev,
-       lwr_now,
-       lwr_offs = { 0.0f, w->collider.radius, 0.0f };
-
-   v3_add( lwr_offs, w->state.prev_pos, lwr_prev );
-   v3_add( lwr_offs, player->rb.co, lwr_now );
-
-   v3f movedelta;
-   v3_sub( player->rb.co, w->state.prev_pos, movedelta );
-
-   float movedist = v3_length( movedelta );
-
-   if( movedist > 0.3f )
-   {
-      float t, sr = w->collider.radius-0.04f;
-      v3f n;
-
-      if( spherecast_world( lwr_prev, lwr_now, sr, &t, n ) != -1 )
-      {
-         v3_lerp( lwr_prev, lwr_now, vg_maxf(0.01f,t), player->rb.co );
-         player->rb.co[1] -= w->collider.radius;
-         rb_update_transform( &player->rb );
-
-         v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
-         debug_capsule( mtx, w->collider.radius, w->collider.height, VG__RED );
-      }
-   }
-
-   teleport_gate *gate;
-   if( (gate = world_intersect_gates( player->rb.co, w->state.prev_pos )) )
-   {
-      struct player_device_walk *w = dev->storage;
-
-      m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
-      m3x3_mulv( gate->transport, player->rb.v,  player->rb.v );
-      rb_update_transform( &player->rb );
-
-      /* analytical rotation of yaw */
-      v3f fwd_dir = { cosf(w->state.angles[0]),
-                      0.0f,
-                      sinf(w->state.angles[0])};
-      m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
-      w->state.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
-
-      w->state_gate_storage = w->state;
-      player_pass_gate( player, gate );
-   }
-}
-
-VG_STATIC void player_walk_post_update( player_device *dev,
-                                        player_interface *player )
-{
-   struct player_device_walk *w = dev->storage;
-
-   m4x3f mtx;
-   m3x3_identity( mtx );
-   v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
-
-   float substep = vg_clampf( vg.accumulator / k_rb_delta, 0.0f, 1.0f );
-   v3_muladds( mtx[3], player->rb.v, k_rb_delta*substep, mtx[3] );
-   debug_capsule( mtx, w->collider.radius, w->collider.height, VG__YELOW );
-
-#if 0
-   player_apply_transport_to_cam( gate->transport );
-#endif
-
-}
-
-VG_STATIC void player_walk_animate( player_device *dev, 
-                                    player_interface *player )
-{
-   struct player_device_walk *w = dev->storage;
-   struct skeleton *sk = &player->playeravatar->sk;
-
-   {
-      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;
-
-      w->blend_fly = vg_lerpf( w->blend_fly, fly, rate*vg.time_delta );
-      w->blend_run = vg_lerpf( w->blend_run,
-                               w->move_speed *
-                               (1.0f + player->input_walk->button.value*0.5f),
-                               2.0f*vg.time_delta );
-   }
-
-   player_pose apose, bpose;
-
-   if( w->move_speed > 0.025f )
-   {
-      /* TODO move */
-      float walk_norm = 30.0f/(float)w->anim_walk->length,
-            run_norm  = 30.0f/(float)w->anim_run->length,
-            walk_adv  = vg_lerpf( walk_norm, run_norm, w->move_speed );
-
-      w->walk_timer += walk_adv * vg.time_delta;
-   }
-   else
-   {
-      w->walk_timer = 0.0f;
-   }
-
-   float walk_norm = (float)w->anim_walk->length/30.0f,
-         run_norm  = (float)w->anim_run->length/30.0f,
-         t = w->walk_timer,
-         l = vg_clampf( w->blend_run*15.0f, 0.0f, 1.0f ),
-         idle_walk = vg_clampf( (w->blend_run-0.1f)/(1.0f-0.1f), 0.0f, 1.0f );
-
-   /* walk/run */
-   skeleton_sample_anim( sk, w->anim_walk, t*walk_norm, apose );
-   skeleton_sample_anim( sk, w->anim_run,  t*run_norm, bpose );
-
-   skeleton_lerp_pose( sk, apose, bpose, l, apose );
-
-   /* idle */
-   skeleton_sample_anim( sk, w->anim_idle, vg.time*0.1f, bpose );
-   skeleton_lerp_pose( sk, apose, bpose, 1.0f-idle_walk, apose );
-
-   /* air */
-   skeleton_sample_anim( sk, w->anim_jump, vg.time*0.6f, bpose );
-   skeleton_lerp_pose( sk, apose, bpose, w->blend_fly, dev->pose );
-
-   /* Create transform */
-   rb_extrapolate( &player->rb, dev->pose_root_co, dev->pose_root_q );
-   q_axis_angle( dev->pose_root_q, (v3f){0.0f,1.0f,0.0f}, 
-                 -w->state.angles[0]-VG_PIf*0.5f );
-}
-
-VG_STATIC void player_walk_post_animate( player_device *dev,
-                                         player_interface *player )
-{
-   /* 
-    * Camera 
-    */
-   struct player_device_walk *w = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-
-   /* 3RD */
-   m3x3f angles;
-   euler_m3x3( w->state.angles, angles );
-
-   v3f cast_dir, origin;
-
-   v3_add( player->rb.co, (v3f){0.0f,2.0f,0.0f}, origin );
-
-   v3_muladds( origin, angles[2], 2.0f, dev->cam_3rd.pos );
-   v3_muladds( dev->cam_3rd.pos, angles[0], 0.5f, dev->cam_3rd.pos );
-
-   float t;
-   v3f n;
-   if( spherecast_world( origin, dev->cam_3rd.pos, 0.1f, &t, n ) != -1 )
-      v3_lerp( origin, dev->cam_3rd.pos, t, dev->cam_3rd.pos );
-   v3_copy( w->state.angles, dev->cam_3rd.angles );
-   dev->cam_3rd.fov = 90.0f;
-
-
-   /* 1ST */
-   /* FIXME: viewpoint entity */
-   v3f vp = {-0.1f,1.8f,0.0f};
-   m4x3_mulv( av->sk.final_mtx[ av->id_head-1 ], vp, dev->cam_1st.pos );
-   v3_copy( w->state.angles, dev->cam_1st.angles );
-   dev->cam_1st.fov = 90.0f;
-
-   /* FIXME: Organize this. Its int wrong fucking place */
-   v3f vp0 = {0.0f,0.1f, 0.6f},
-       vp1 = {0.0f,0.1f,-0.6f};
-
-   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp0, TEMP_BOARD_0 );
-   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp1, TEMP_BOARD_1 );
-}
-
-
-VG_STATIC void player_walk_ui( player_device *dev, player_interface *player )
-{
-   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] );
-}
-
-VG_STATIC void player_walk_bind( player_device *dev, player_interface *player )
-{
-   struct player_device_walk *w = dev->storage;
-   struct player_avatar *av = player->playeravatar;
-   struct skeleton *sk = &av->sk;
-
-   w->anim_idle = skeleton_get_anim( sk, "idle_cycle" );
-   w->anim_walk = skeleton_get_anim( sk, "walk" );
-   w->anim_run  = skeleton_get_anim( sk, "run" );
-   w->anim_jump = skeleton_get_anim( sk, "jump" );
-
-   w->device_id_skate = player_get_device( player, "skateboard" );
-}
-
-VG_STATIC int player_walk_event( player_device *dev, player_interface *player,
-                                 enum player_device_event_type ev, void *data )
-{
-   struct player_device_walk *w = dev->storage;
-
-   if( ev == k_player_device_event_bind )
-   {
-      player_walk_bind( dev, player );
-   }
-   else if( ev == k_player_device_event_custom_transition )
-   {
-      struct device_transition_walk *inf = data;
-      v3_copy( inf->angles, w->state.angles );
-   }
-   else if( ev == k_player_device_event_pre_update )
-   {
-      player_walk_pre_update( dev, player );
-   }
-   else if( ev == k_player_device_event_update )
-   {
-      player_walk_update( dev, player );
-   }
-   else if( ev == k_player_device_event_post_update )
-   {
-      player_walk_post_update( dev, player );
-   }
-   else if( ev == k_player_device_event_animate )
-   {
-      player_walk_animate( dev, player );
-   }
-   else if( ev == k_player_device_event_post_animate )
-   {
-      player_walk_post_animate( dev, player );
-   }
-   else if( ev == k_player_device_event_debug_ui )
-   {
-      player_walk_ui( dev, player );
-   }
-   else
-      return 0;
-
-   return 1;
-}
-
-VG_STATIC player_device player_device_walk =
-{
-   .name          = "walk",
-   .event         = player_walk_event,
-   .storage       = &localplayer_device_walk
-};
-
-#endif /* PLAYER_DEVICE_WALK_H */
index 38b98b776ca4647fd72b4eaa624a412f258f37f8..dc23776500b58bfd163a8770b29f354c8a507a58 100644 (file)
@@ -1,3 +1,4 @@
+#if 0
 #ifndef PLAYER_INTERFACE_H
 #define PLAYER_INTERFACE_H
 
@@ -494,3 +495,4 @@ void player_look( player_interface *player, v3f angles )
 }
 
 #endif /* PLAYER_INTERFACE_H */
+#endif
index 7bf801ce8f2935f232ca370c755d5ab75957cc5c..6c46f08e4c18601048272135d1740f3f4cca3885 100644 (file)
@@ -8,10 +8,6 @@
 #define VG_GAME
 #include "vg/vg.h"
 
-#if 0
-#include "player.h"
-#endif
-
 #include "model.h"
 #include "skeleton.h"
 #include "player_ragdoll.h"
@@ -24,20 +20,6 @@ struct player_avatar
    mdl_context meta;
    struct skeleton sk;
 
-   v3f cam_pos;   /* FIXME ? */
-
-#if 0
-   struct skeleton_anim *anim_stand,
-                        *anim_highg,
-                        *anim_slide,
-                        *anim_air,
-                        *anim_push,  *anim_push_reverse,
-                        *anim_ollie, *anim_ollie_reverse,
-                        *anim_grabs, *anim_stop,
-                        *anim_walk,  *anim_run, *anim_idle,
-                        *anim_jump;
-#endif
-
    u32 id_hip,
        id_ik_hand_l,
        id_ik_hand_r,
diff --git a/player_physics.h b/player_physics.h
deleted file mode 100644 (file)
index 73bad63..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#define PLAYER_PHYSICS_H
-#ifndef PLAYER_PHYSICS_H
-#define PLAYER_PHYSICS_H
-
-#include "player.h"
-#include "camera.h"
-
-VG_STATIC void player_integrate(void);
-
-VG_STATIC int player_collide_sphere( rigidbody *rb, rb_ct *manifold )
-{
-#if 0
-   int len = 0;
-
-   len = rb_sphere_scene( rb, &world.rb_geo, manifold );
-   rb_manifold_filter_coplanar( manifold, len, 0.05f );
-   if( len > 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
-      len = new_len;
-
-   return len;
-#endif
-
-   return 0;
-}
-
-#if 0
-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 void player_integrate(void)
-{
-   struct player_phys *phys = &player.phys;
-   v3_sub( phys->rb.v, phys->v_last, phys->a );
-   v3_muls( phys->a, 1.0f/VG_TIMESTEP_FIXED, phys->a );
-   v3_copy( phys->rb.v, phys->v_last );
-
-   apply_gravity( phys->rb.v, VG_TIMESTEP_FIXED );
-   v3_muladds( phys->rb.co, phys->rb.v, VG_TIMESTEP_FIXED, phys->rb.co );
-}
-#endif
-
-VG_STATIC void player_freecam(void)
-{
-   player_mouseview();
-
-   float movespeed = fc_speed * VG_TIMESTEP_FIXED;
-   v3f lookdir = { 0.0f, 0.0f, -1.0f },
-       sidedir = { 1.0f, 0.0f,  0.0f };
-   
-   m3x3_mulv( main_camera.transform, lookdir, lookdir );
-   m3x3_mulv( main_camera.transform, sidedir, sidedir );
-   
-   static v3f move_vel = { 0.0f, 0.0f, 0.0f };
-
-   v2f steer = { player.input_js1h->axis.value,
-                 player.input_js1v->axis.value };
-
-   v3_muladds( move_vel, sidedir,  movespeed*steer[0], move_vel );
-   v3_muladds( move_vel, lookdir, -movespeed*steer[1], move_vel );
-
-   v3_muls( move_vel, 0.7f, move_vel );
-   v3_add( move_vel, player.camera_pos, player.camera_pos );
-}
-
-VG_STATIC int kill_player( int argc, char const *argv[] )
-{
-   player_kill();
-   return 0;
-}
-
-VG_STATIC int reset_player( int argc, char const *argv[] )
-{
-   struct 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, player.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" );
-      vg_info( "Player position: %f %f %f\n", player.co[0],
-                                              player.co[1],
-                                              player.co[2] );
-
-      if( !world.spawn_count )
-         return 0;
-
-      rp = &world.spawns[0];
-   }
-
-   player.is_dead = 0;
-
-   m3x3f the_long_way;
-   q_m3x3( rp->q, the_long_way );
-
-   v3f delta = {1.0f,0.0f,0.0f};
-   m3x3_mulv( the_long_way, delta, delta );
-   
-   if( !freecam )
-   {
-      player.angles[0] = atan2f( delta[0], -delta[2] ); 
-      player.angles[1] = -asinf( delta[1] );
-   }
-
-   player.controller = k_player_controller_walk;
-   /* TODO: trigger controller slurp */
-
-   player_save_frame();
-   return 1;
-}
-
-VG_STATIC void reset_player_poll( int argc, char const *argv[] )
-{
-   if( argc == 1 )
-   {
-      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 );
-      }
-   }
-}
-
-#endif /* PLAYER_PHYSICS_H */
diff --git a/player_physics_skate.h b/player_physics_skate.h
deleted file mode 100644 (file)
index f4b960f..0000000
+++ /dev/null
@@ -1,1117 +0,0 @@
-/*
- * 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
deleted file mode 100644 (file)
index 35dbf29..0000000
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * 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 */
diff --git a/player_skate.c b/player_skate.c
new file mode 100644 (file)
index 0000000..fedeee8
--- /dev/null
@@ -0,0 +1,1600 @@
+#ifndef PLAYER_SKATE_C
+#define PLAYER_SKATE_C
+
+#include "player.h"
+
+VG_STATIC void player__skate_bind( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   struct player_avatar *av = player->playeravatar;
+   struct skeleton *sk = &av->sk;
+
+   rb_update_transform( &player->rb );
+   s->anim_stand           = skeleton_get_anim( sk, "pose_stand" );
+   s->anim_highg           = skeleton_get_anim( sk, "pose_highg" );
+   s->anim_air             = skeleton_get_anim( sk, "pose_air" );
+   s->anim_slide           = skeleton_get_anim( sk, "pose_slide" );
+   s->anim_push            = skeleton_get_anim( sk, "push" );
+   s->anim_push_reverse    = skeleton_get_anim( sk, "push_reverse" );
+   s->anim_ollie           = skeleton_get_anim( sk, "ollie" );
+   s->anim_ollie_reverse   = skeleton_get_anim( sk, "ollie_reverse" );
+   s->anim_grabs           = skeleton_get_anim( sk, "grabs" );
+}
+
+/* 
+ * Collision detection routines
+ *
+ *
+ */
+
+/*
+ * Does collision detection on a sphere vs world, and applies some smoothing
+ * filters to the manifold afterwards
+ */
+VG_STATIC int skate_collide_smooth( player_instance *player,
+                                    m4x3f mtx, rb_sphere *sphere,
+                                    rb_ct *man )
+{
+   debug_sphere( mtx, sphere->radius, VG__BLACK );
+
+   int len = 0;
+   len = rb_sphere__scene( mtx, sphere, NULL, &world.rb_geo.inf.scene, man );
+
+   for( int i=0; i<len; i++ )
+   {
+      man[i].rba = &player->rb;
+      man[i].rbb = NULL;
+   }
+
+   rb_manifold_filter_coplanar( man, len, 0.05f );
+
+   if( len > 1 )
+   {
+      rb_manifold_filter_backface( man, len );
+      rb_manifold_filter_joint_edges( man, len, 0.05f );
+      rb_manifold_filter_pairs( man, len, 0.05f );
+   }
+   int new_len = rb_manifold_apply_filtered( man, len );
+   if( len && !new_len )
+      len = 1;
+   else
+      len = new_len;
+
+   return len;
+}
+/*
+ * Gets the closest grindable edge to the player within max_dist 
+ */
+VG_STATIC struct grind_edge *skate_collect_grind_edge( v3f p0, v3f p1,
+                                                       v3f c0, v3f c1, 
+                                                       float max_dist )
+{
+   bh_iter it;
+   bh_iter_init( 0, &it );
+
+   boxf region;
+
+   box_init_inf( region );
+   box_addpt( region, p0 );
+   box_addpt( region, p1 );
+   
+   float k_r = max_dist;
+   v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] );
+   v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] );
+
+   float closest = k_r*k_r;
+   struct grind_edge *closest_edge = NULL;
+   
+   int idx;
+   while( bh_next( world.grind_bh, &it, region, &idx ) )
+   {
+      struct grind_edge *edge = &world.grind_edges[ idx ];
+
+      float s,t;
+      v3f pa, pb;
+
+      float d2 = 
+         closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb );
+
+      if( d2 < closest )
+      {
+         closest = d2;
+         closest_edge = edge;
+         v3_copy( pa, c0 );
+         v3_copy( pb, c1 );
+      }
+   }
+
+   return closest_edge;
+}
+
+VG_STATIC int skate_grind_collide( player_instance *player, rb_ct *contact )
+{
+   v3f p0, p1, c0, c1;
+   v3_muladds( player->rb.co, player->rb.to_world[2],  0.5f, p0 );
+   v3_muladds( player->rb.co, player->rb.to_world[2], -0.5f, p1 );
+   v3_muladds( p0, player->rb.to_world[1], 0.125f-0.15f, p0 );
+   v3_muladds( p1, player->rb.to_world[1], 0.125f-0.15f, p1 );
+
+   float const k_r = 0.25f;
+   struct grind_edge *closest_edge = skate_collect_grind_edge( p0, p1, 
+                                                               c0, c1, k_r );
+
+   if( closest_edge )
+   {
+      v3f delta;
+      v3_sub( c1, c0, delta );
+
+      if( v3_dot( delta, player->rb.to_world[1] ) > 0.0001f )
+      {
+         contact->p = v3_length( delta );
+         contact->type = k_contact_type_edge;
+         contact->element_id = 0;
+         v3_copy( c1, contact->co );
+         contact->rba = NULL;
+         contact->rbb = NULL;
+
+         v3f edge_dir, axis_dir;
+         v3_sub( closest_edge->p1, closest_edge->p0, edge_dir );
+         v3_normalize( edge_dir );
+         v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
+         v3_cross( edge_dir, axis_dir, contact->n );
+
+         return 1;
+      }
+      else
+         return 0;
+   }
+
+   return 0;
+}
+
+/*
+ *
+ * Prediction system
+ *
+ *
+ */
+
+/* 
+ * Trace a path given a velocity rotation.
+ *
+ * TODO: this MIGHT be worth doing RK4 on the gravity field.
+ */
+VG_STATIC void skate_score_biased_path( v3f co, v3f v, m3x3f vr, 
+                                        struct land_prediction *prediction )
+{
+   float pstep  = VG_TIMESTEP_FIXED * 10.0f;
+   float k_bias = 0.96f;
+
+   v3f pco, pco1, pv;
+   v3_copy( co, pco );
+   v3_muls( v,  k_bias, pv );
+
+   m3x3_mulv( vr, pv, pv );
+   v3_muladds( pco, pv, pstep, pco );
+
+   struct grind_edge *best_grind = NULL;
+   float closest_grind = INFINITY;
+
+   float grind_score    = INFINITY,
+         air_score      = INFINITY,
+         time_to_impact = 0.0f;
+
+   prediction->log_length = 0;
+   v3_copy( pco, prediction->apex );
+
+   for( int i=0; i<vg_list_size(prediction->log); i++ )
+   {
+      v3_copy( pco, pco1 );
+
+      pv[1] += -k_gravity * pstep;
+
+      m3x3_mulv( vr, pv, pv );
+      v3_muladds( pco, pv, pstep, pco );
+
+      if( pco[1] > prediction->apex[1] )
+         v3_copy( pco, prediction->apex );
+      
+      v3f vdir;
+
+      v3_sub( pco, pco1, vdir );
+
+      float l = v3_length( vdir );
+      v3_muls( vdir, 1.0f/l, vdir );
+
+      v3f c0, c1;
+      struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
+                                                        c0, c1, 0.4f );
+
+      if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
+      {
+         float d2 = v3_dist2( c0, c1 );
+         if( d2 < closest_grind )
+         {
+            closest_grind = d2;
+            best_grind = ge;
+            grind_score = closest_grind * 0.05f;
+         }
+      }
+
+      v3f n1;
+
+      float t1;
+      int idx = spherecast_world( pco1, pco, 0.4f, &t1, n1 );
+      if( idx != -1 )
+      {
+         v3_copy( n1, prediction->n );
+         air_score = -v3_dot( pv, n1 );
+         
+         u32 vert_index = world.scene_geo->arrindices[ idx*3 ];
+         struct world_material *mat = world_tri_index_material( vert_index );
+
+         /* Bias prediction towords ramps */
+         if( mat->info.flags & k_material_flag_skate_surface )
+            air_score *= 0.1f;
+
+         v3_lerp( pco1, pco, t1, prediction->log[ prediction->log_length ++ ] ); 
+         time_to_impact += t1 * pstep;
+         break;
+      }
+
+      time_to_impact += pstep;
+      v3_copy( pco, prediction->log[ prediction->log_length ++ ] );
+   }
+
+   if( grind_score < air_score )
+   {
+      prediction->score = grind_score; 
+      prediction->type = k_prediction_grind;
+   }
+   else if( air_score < INFINITY )
+   {
+      prediction->score = air_score;
+      prediction->type = k_prediction_land;
+   }
+   else
+   {
+      prediction->score = INFINITY;
+      prediction->type = k_prediction_none;
+   }
+
+   prediction->land_dist = time_to_impact;
+}
+
+VG_STATIC 
+void player_approximate_best_trajectory( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   float pstep = VG_TIMESTEP_FIXED * 10.0f;
+   float best_velocity_delta = -9999.9f;
+
+   v3f axis;
+   v3_cross( player->rb.to_world[1], player->rb.v, axis );
+   v3_normalize( axis );
+
+   s->prediction_count = 0;
+   m3x3_identity( s->state.velocity_bias );
+
+   float best_vmod   = 0.0f,
+         min_score   =  INFINITY,
+         max_score   = -INFINITY;
+
+   v3_zero( s->state.apex );
+   s->land_dist = 0.0f;
+
+   /*
+    * Search a broad selection of futures
+    */
+   for( int m=-3;m<=12; m++ )
+   {
+      struct land_prediction *p = &s->predictions[ s->prediction_count ++ ];
+
+      float vmod = ((float)m / 15.0f)*0.09f;
+
+      m3x3f bias;
+      v4f bias_q;
+
+      q_axis_angle( bias_q, axis, vmod );
+      q_m3x3( bias_q, bias );
+
+      skate_score_biased_path( player->rb.co, player->rb.v, bias, p );
+
+      if( p->type != k_prediction_none )
+      {
+         if( p->score < min_score )
+         {
+            min_score = p->score;
+            best_vmod = vmod;
+            s->land_dist = p->land_dist;
+            v3_copy( p->apex, s->state.apex );
+         }
+
+         if( p->score > max_score )
+            max_score = p->score;
+      }
+   }
+
+   v4f vr_q;
+   q_axis_angle( vr_q, axis, best_vmod*0.1f );
+   q_m3x3( vr_q, s->state.velocity_bias );
+
+   q_axis_angle( vr_q, axis, best_vmod );
+   q_m3x3( vr_q, s->state.velocity_bias_pstep );
+
+   /*
+    * Logging
+    */
+   for( int i=0; i<s->prediction_count; i ++ )
+   {
+      struct land_prediction *p = &s->predictions[i];
+
+      float l = p->score;
+
+      if( l < 0.0f )
+      {
+         vg_error( "negative score! (%f)\n", l );
+      }
+
+      l -= min_score;
+      l /= (max_score-min_score);
+      l  = 1.0f - l;
+      l *= 255.0f;
+
+      p->colour = l;
+      p->colour <<= 8;
+      p->colour |= 0xff000000;
+   }
+
+
+   v2f steer = { player->input_js1h->axis.value,
+                 player->input_js1v->axis.value };
+   v2_normalize_clamp( steer );
+
+   if( (fabsf(steer[1]) > 0.5f) && (s->land_dist >= 1.0f) )
+   {
+      s->state.flip_rate = (1.0f/s->land_dist) * vg_signf(steer[1]) *
+                              s->state.reverse ;
+      s->state.flip_time = 0.0f;
+      v3_copy( player->rb.to_world[0], s->state.flip_axis );
+   }
+   else
+   {
+      s->state.flip_rate = 0.0f;
+      v3_zero( s->state.flip_axis );
+   }
+}
+
+/*
+ *
+ * Varius physics models
+ * ------------------------------------------------
+ */
+
+VG_STATIC void skate_apply_grind_model( player_instance *player,
+                                        rb_ct *manifold, int len )
+{
+   struct player_skate *s = &player->_skate;
+
+   /* FIXME: Queue audio events instead */
+   if( len == 0 )
+   {
+      if( s->state.activity == k_skate_activity_grind )
+      {
+#if 0
+         audio_lock();
+         audio_player_set_flags( &audio_player_extra, 
+                                 AUDIO_FLAG_SPACIAL_3D );
+         audio_player_set_position( &audio_player_extra, player.rb.co );
+         audio_player_set_vol( &audio_player_extra, 20.0f );
+         audio_player_playclip( &audio_player_extra, &audio_board[6] );
+         audio_unlock();
+#endif
+
+         s->state.activity = k_skate_activity_air;
+      }
+      return;
+   }
+
+   v2f steer = { player->input_js1h->axis.value,
+                 player->input_js1v->axis.value };
+   v2_normalize_clamp( steer );
+
+   s->state.steery -= steer[0] * k_steer_air * k_rb_delta;
+   s->state.steerx += steer[1] * s->state.reverse * k_steer_air * k_rb_delta;
+   
+#if 0
+   v4f rotate;
+   q_axis_angle( rotate, player->rb.to_world[0], siX );
+   q_mul( rotate, player.rb.q, player.rb.q );
+#endif
+
+   s->state.slip = 0.0f;
+   s->state.activity = k_skate_activity_grind;
+
+   /* TODO: Compression */
+   v3f up = { 0.0f, 1.0f, 0.0f };
+   float angle = v3_dot( player->rb.to_world[1], up );
+
+   if( fabsf(angle) < 0.99f )
+   {
+      v3f axis; 
+      v3_cross( player->rb.to_world[1], up, axis );
+
+      v4f correction;
+      q_axis_angle( correction, axis, k_rb_delta * 10.0f * acosf(angle) );
+      q_mul( correction, player->rb.q, player->rb.q );
+   }
+
+   float const DOWNFORCE = -k_downforce*1.2f*VG_TIMESTEP_FIXED;
+   v3_muladds( player->rb.v, manifold->n, DOWNFORCE, player->rb.v );
+   m3x3_identity( s->state.velocity_bias );
+   m3x3_identity( s->state.velocity_bias_pstep );
+
+   if( s->state.activity_prev != k_skate_activity_grind )
+   {
+      /* FIXME: Queue audio events instead */
+#if 0
+      audio_lock();
+      audio_player_set_flags( &audio_player_extra, 
+                              AUDIO_FLAG_SPACIAL_3D );
+      audio_player_set_position( &audio_player_extra, player.rb.co );
+      audio_player_set_vol( &audio_player_extra, 20.0f );
+      audio_player_playclip( &audio_player_extra, &audio_board[5] );
+      audio_unlock();
+#endif
+   }
+}
+
+/*
+ * Air control, no real physics
+ */
+VG_STATIC void skate_apply_air_model( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   if( s->state.activity != k_skate_activity_air )
+      return;
+
+   if( s->state.activity_prev != k_skate_activity_air )
+      player_approximate_best_trajectory( player );
+
+   m3x3_mulv( s->state.velocity_bias, player->rb.v, player->rb.v );
+   ray_hit hit;
+
+   /* 
+    * Prediction 
+    */
+   float pstep  = VG_TIMESTEP_FIXED * 1.0f;
+   float k_bias = 0.98f;
+
+   v3f pco, pco1, pv;
+   v3_copy( player->rb.co, pco );
+   v3_muls( player->rb.v, 1.0f, pv );
+   
+   float time_to_impact = 0.0f;
+   float limiter = 1.0f;
+
+   struct grind_edge *best_grind = NULL;
+   float closest_grind = INFINITY;
+
+   v3f target_normal = { 0.0f, 1.0f, 0.0f };
+   int has_target = 0;
+
+   for( int i=0; i<250; i++ )
+   {
+      v3_copy( pco, pco1 );
+      m3x3_mulv( s->state.velocity_bias, pv, pv );
+
+      pv[1] += -k_gravity * pstep;
+      v3_muladds( pco, pv, pstep, pco );
+      
+      ray_hit contact;
+      v3f vdir;
+
+      v3_sub( pco, pco1, vdir );
+      contact.dist = v3_length( vdir );
+      v3_divs( vdir, contact.dist, vdir);
+
+      v3f c0, c1;
+      struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
+                                                        c0, c1, 0.4f );
+
+      if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) ) 
+      { 
+         vg_line( ge->p0, ge->p1, 0xff0000ff ); 
+         vg_line_cross( pco, 0xff0000ff, 0.25f );
+         has_target = 1;
+         break;
+      }
+      
+      float orig_dist = contact.dist;
+      if( ray_world( pco1, vdir, &contact ) )
+      {
+         v3_copy( contact.normal, target_normal );
+         has_target = 1;
+         time_to_impact += (contact.dist/orig_dist)*pstep;
+         vg_line_cross( contact.pos, 0xffff0000, 0.25f );
+         break;
+      }
+      time_to_impact += pstep;
+   }
+
+   if( has_target )
+   {
+      float angle = v3_dot( player->rb.to_world[1], target_normal );
+      v3f axis; 
+      v3_cross( player->rb.to_world[1], target_normal, axis );
+
+      limiter = vg_minf( 5.0f, time_to_impact )/5.0f;
+      limiter = 1.0f-limiter;
+      limiter *= limiter;
+      limiter = 1.0f-limiter;
+
+      if( fabsf(angle) < 0.99f )
+      {
+         v4f correction;
+         q_axis_angle( correction, axis, 
+                        acosf(angle)*(1.0f-limiter)*2.0f*VG_TIMESTEP_FIXED );
+         q_mul( correction, player->rb.q, player->rb.q );
+      }
+   }
+
+   v2f steer = { player->input_js1h->axis.value,
+                 player->input_js1v->axis.value };
+   v2_normalize_clamp( steer );
+
+   s->state.steery -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
+   s->state.steerx += steer[1] * s->state.reverse * k_steer_air 
+                                                         * limiter * k_rb_delta;
+   s->land_dist = time_to_impact;
+   v3_copy( target_normal, s->land_normal );
+}
+
+VG_STATIC void skate_get_board_points( player_instance *player,
+                                       v3f front, v3f back )
+{
+   v3f pos_front = {0.0f,0.0f,-k_board_length},
+       pos_back  = {0.0f,0.0f, k_board_length};
+
+   m4x3_mulv( player->rb.to_world, pos_front, front );
+   m4x3_mulv( player->rb.to_world, pos_back,  back );
+}
+
+/*
+ * Casts and pushes a sphere-spring model into the world
+ */
+VG_STATIC int skate_simulate_spring( player_instance *player,
+                                     v3f pos )
+{
+   struct player_skate *s = &player->_skate;
+
+   float mod      = 0.7f * player->input_grab->axis.value + 0.3f,
+         spring_k = mod * k_spring_force,
+         damp_k   = mod * k_spring_dampener,
+         disp_k   = 0.4f;
+
+   v3f start, end;
+   v3_copy( pos, start );
+   v3_muladds( pos, player->rb.to_world[1], -disp_k, end );
+
+   float t;
+   v3f n;
+   int hit_info = spherecast_world( start, end, 0.2f, &t, n );
+
+   if( hit_info != -1 )
+   {
+      v3f F, delta;
+      v3_sub( start, player->rb.co, delta );
+      
+      float displacement = vg_clampf( 1.0f-t, 0.0f, 1.0f ),
+            damp         = 
+         vg_maxf( 0.0f, v3_dot( player->rb.to_world[1], player->rb.v ) );
+
+      v3_muls( player->rb.to_world[1], displacement*spring_k*k_rb_delta -
+                                       damp*damp_k*k_rb_delta, F );
+
+      v3_muladds( player->rb.v, F, 1.0f, player->rb.v );
+      
+      /* Angular velocity */
+      v3f wa;
+      v3_cross( delta, F, wa );
+      v3_muladds( player->rb.w, wa, k_spring_angular, player->rb.w );
+
+      v3_lerp( start, end, t, pos );
+      return 1;
+   }
+   else
+   {
+      v3_copy( end, pos );
+      return 0;
+   }
+}
+
+
+/* 
+ * Handles connection between the player and the ground
+ */
+VG_STATIC void skate_apply_interface_model( player_instance *player,
+                                            rb_ct *manifold, int len )
+{
+   struct player_skate *s = &player->_skate;
+
+   if( !((s->state.activity == k_skate_activity_ground) ||
+         (s->state.activity == k_skate_activity_air )) )
+      return;
+
+   if( s->state.activity == k_skate_activity_air )
+      s->debug_normal_pressure = 0.0f;
+   else
+      s->debug_normal_pressure = v3_dot( player->rb.to_world[1], player->rb.v );
+
+   /* springs */
+   v3f spring0, spring1;
+
+   skate_get_board_points( player, spring1, spring0 );
+   int spring_hit0 = 0, //skate_simulate_spring( player, s, spring0 ),
+       spring_hit1 = 0; //skate_simulate_spring( player, s, spring1 );
+
+   v3f animavg, animdelta;
+   v3_add( spring0, spring1, animavg );
+   v3_muls( animavg, 0.5f, animavg );
+
+   v3_sub( spring1, spring0, animdelta );
+   v3_normalize( animdelta );
+
+   m4x3_mulv( player->rb.to_local, animavg, s->board_offset );
+
+   float dx = -v3_dot( animdelta, player->rb.to_world[2] ),
+         dy =  v3_dot( animdelta, player->rb.to_world[1] );
+
+   float angle = -atan2f( dy, dx );
+   q_axis_angle( s->board_rotation, (v3f){1.0f,0.0f,0.0f}, angle );
+
+   int lift_frames_limit = 6;
+
+   /* Surface connection */
+   if( len == 0 && !(spring_hit0 && spring_hit1) )
+   {
+      s->state.lift_frames ++;
+
+      if( s->state.lift_frames >= lift_frames_limit )
+         s->state.activity = k_skate_activity_air;
+   }
+   else
+   {
+      v3f surface_avg;
+      v3_zero( surface_avg );
+
+      for( int i=0; i<len; i++ )
+         v3_add( surface_avg, manifold[i].n, surface_avg );
+      v3_normalize( surface_avg );
+      
+      if( v3_dot( player->rb.v, surface_avg ) > 0.7f )
+      {
+         s->state.lift_frames ++;
+
+         if( s->state.lift_frames >= lift_frames_limit )
+            s->state.activity = k_skate_activity_air;
+      }
+      else
+      {
+         s->state.activity = k_skate_activity_ground;
+         s->state.lift_frames = 0;
+         v3f projected, axis;
+
+         float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
+         v3_muladds( player->rb.v, player->rb.to_world[1], 
+                     DOWNFORCE, player->rb.v );
+
+         float d = v3_dot( player->rb.to_world[2], surface_avg );
+         v3_muladds( surface_avg, player->rb.to_world[2], -d, projected );
+         v3_normalize( projected );
+
+         float angle = v3_dot( player->rb.to_world[1], projected );
+         v3_cross( player->rb.to_world[1], projected, axis );
+
+         if( fabsf(angle) < 0.9999f )
+         {
+            v4f correction;
+            q_axis_angle( correction, axis, 
+                          acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
+            q_mul( correction, player->rb.q, player->rb.q );
+         }
+      }
+   }
+}
+
+VG_STATIC void skate_apply_grab_model( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   float grabt = player->input_grab->axis.value;
+
+   if( grabt > 0.5f )
+   {
+      v2_muladds( s->state.grab_mouse_delta, vg.mouse_delta, 0.02f, 
+                  s->state.grab_mouse_delta );
+
+      v2_normalize_clamp( s->state.grab_mouse_delta );
+   }
+   else
+      v2_zero( s->state.grab_mouse_delta );
+
+   s->state.grabbing = vg_lerpf( s->state.grabbing, grabt, 8.4f*k_rb_delta );
+}
+
+/*
+ * Computes friction and surface interface model
+ */
+VG_STATIC void skate_apply_friction_model( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   if( s->state.activity != k_skate_activity_ground )
+      return;
+
+   /*
+    * Computing localized friction forces for controlling the character
+    * Friction across X is significantly more than Z
+    */
+
+   v3f vel;
+   m3x3_mulv( player->rb.to_local, player->rb.v, vel );
+   float slip = 0.0f;
+   
+   if( fabsf(vel[2]) > 0.01f )
+      slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
+
+   if( fabsf( slip ) > 1.2f )
+      slip = vg_signf( slip ) * 1.2f;
+
+   s->state.slip = slip;
+   s->state.reverse = -vg_signf(vel[2]);
+
+   vel[0] += vg_cfrictf( vel[0], k_friction_lat * k_rb_delta );
+   vel[2] += vg_cfrictf( vel[2], k_friction_resistance * k_rb_delta );
+
+   /* Pushing additive force */
+
+   if( !player->input_jump->button.value )
+   {
+      if( player->input_push->button.value )
+      {
+         if( (vg.time - s->state.cur_push) > 0.25 )
+            s->state.start_push = vg.time;
+
+         s->state.cur_push = vg.time;
+
+         double push_time = vg.time - s->state.start_push;
+
+         float cycle_time = push_time*k_push_cycle_rate,
+               accel      = k_push_accel * (sinf(cycle_time)*0.5f+0.5f),
+               amt        = accel * VG_TIMESTEP_FIXED,
+               current    = v3_length( vel ),
+               new_vel    = vg_minf( current + amt, k_max_push_speed ),
+               delta      = new_vel - vg_minf( current, k_max_push_speed );
+
+         vel[2] += delta * -s->state.reverse;
+      }
+   }
+
+   /* Send back to velocity */
+   m3x3_mulv( player->rb.to_world, vel, player->rb.v );
+   
+   /* Steering */
+   float input = player->input_js1h->axis.value,
+         grab  = player->input_grab->axis.value,
+         steer = input * (1.0f-(s->state.jump_charge+grab)*0.4f),
+         steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
+
+   s->state.steery -= steer_scaled * k_rb_delta;
+}
+
+VG_STATIC void skate_apply_jump_model( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   int charging_jump_prev = s->state.charging_jump;
+   s->state.charging_jump = player->input_jump->button.value;
+
+   /* Cannot charge this in air */
+   if( s->state.activity != k_skate_activity_ground )
+      s->state.charging_jump = 0;
+
+   if( s->state.charging_jump )
+   {
+      s->state.jump_charge += k_rb_delta * k_jump_charge_speed;
+
+      if( !charging_jump_prev )
+         s->state.jump_dir = s->state.reverse>0.0f? 1: 0;
+   }
+   else
+   {
+      s->state.jump_charge -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
+   }
+
+   s->state.jump_charge = vg_clampf( s->state.jump_charge, 0.0f, 1.0f );
+
+   if( s->state.activity == k_skate_activity_air )
+      return;
+
+   /* player let go after charging past 0.2: trigger jump */
+   if( (!s->state.charging_jump) && (s->state.jump_charge > 0.2f) )
+   {
+      v3f jumpdir;
+      
+      /* Launch more up if alignment is up else improve velocity */
+      float aup = v3_dot( (v3f){0.0f,1.0f,0.0f}, player->rb.to_world[1] ),
+            mod = 0.5f,
+            dir = mod + fabsf(aup)*(1.0f-mod);
+
+      v3_copy( player->rb.v, jumpdir );
+      v3_normalize( jumpdir );
+      v3_muls( jumpdir, 1.0f-dir, jumpdir );
+      v3_muladds( jumpdir, player->rb.to_world[1], dir, jumpdir );
+      v3_normalize( jumpdir );
+      
+      float force = k_jump_force*s->state.jump_charge;
+      v3_muladds( player->rb.v, jumpdir, force, player->rb.v );
+      s->state.jump_charge = 0.0f;
+
+      s->state.jump_time = vg.time;
+
+      v2f steer = { player->input_js1h->axis.value,
+                    player->input_js1v->axis.value };
+      v2_normalize_clamp( steer );
+
+      float maxspin = k_steer_air * k_rb_delta * k_spin_boost;
+      s->state.steery_s = -steer[0] * maxspin;
+      s->state.steerx = s->state.steerx_s;
+
+      /* FIXME audio events */
+#if 0
+      audio_lock();
+      audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
+      audio_player_set_position( &audio_player_extra, player.rb.co );
+      audio_player_set_vol( &audio_player_extra, 20.0f );
+      audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
+      audio_unlock();
+#endif
+   }
+}
+
+VG_STATIC void skate_apply_pump_model( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   /* Throw / collect routine 
+    *
+    * TODO: Max speed boost
+    */
+   if( player->input_grab->axis.value > 0.5f )
+   {
+      if( s->state.activity == k_skate_activity_ground )
+      {
+         /* Throw */
+         v3_muls( player->rb.to_world[1], k_mmthrow_scale, s->state.throw_v );
+      }
+   }
+   else
+   {
+      /* Collect */
+      float doty = v3_dot( player->rb.to_world[1], s->state.throw_v );
+      
+      v3f Fl, Fv;
+      v3_muladds( s->state.throw_v, player->rb.to_world[1], -doty, Fl);
+
+      if( s->state.activity == k_skate_activity_ground )
+      {
+         v3_muladds( player->rb.v,     Fl,  k_mmcollect_lat, player->rb.v );
+         v3_muladds( s->state.throw_v, Fl, -k_mmcollect_lat, s->state.throw_v );
+      }
+
+      v3_muls( player->rb.to_world[1], -doty, Fv );
+      v3_muladds( player->rb.v,     Fv, k_mmcollect_vert, player->rb.v );
+      v3_muladds( s->state.throw_v, Fv, k_mmcollect_vert, s->state.throw_v );
+   }
+
+   /* Decay */
+   if( v3_length2( s->state.throw_v ) > 0.0001f )
+   {
+      v3f dir;
+      v3_copy( s->state.throw_v, dir );
+      v3_normalize( dir );
+
+      float max = v3_dot( dir, s->state.throw_v ),
+            amt = vg_minf( k_mmdecay * k_rb_delta, max );
+      v3_muladds( s->state.throw_v, dir, -amt, s->state.throw_v );
+   }
+}
+
+VG_STATIC void skate_apply_cog_model( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   v3f ideal_cog, ideal_diff;
+   v3_muladds( player->rb.co, player->rb.to_world[1],
+               1.0f-player->input_grab->axis.value, ideal_cog );
+   v3_sub( ideal_cog, s->state.cog, ideal_diff );
+
+   /* Apply velocities */
+   v3f rv;
+   v3_sub( player->rb.v, s->state.cog_v, rv );
+
+   v3f F;
+   v3_muls( ideal_diff, -k_cog_spring * k_rb_rate, F );
+   v3_muladds( F, rv,   -k_cog_damp * k_rb_rate, F );
+
+   float ra = k_cog_mass_ratio,
+         rb = 1.0f-k_cog_mass_ratio;
+
+   /* Apply forces & intergrate */
+   v3_muladds( s->state.cog_v, F, -rb, s->state.cog_v );
+   s->state.cog_v[1] += -9.8f * k_rb_delta;
+   v3_muladds( s->state.cog, s->state.cog_v, k_rb_delta, s->state.cog );
+}
+
+VG_STATIC void skate_collision_response( player_instance *player,
+                                         rb_ct *manifold, int len )
+{
+   struct player_skate *s = &player->_skate;
+
+   for( int j=0; j<10; j++ )
+   {
+      for( int i=0; i<len; i++ )
+      {
+         struct contact *ct = &manifold[i];
+         
+         v3f dv, delta;
+         v3_sub( ct->co, player->rb.co, delta ); 
+         v3_cross( player->rb.w, delta, dv );
+         v3_add( player->rb.v, dv, dv );
+
+         float vn = -v3_dot( dv, ct->n );
+         vn += ct->bias;
+
+         float temp = ct->norm_impulse;
+         ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
+         vn = ct->norm_impulse - temp;
+
+         v3f impulse;
+         v3_muls( ct->n, vn, impulse );
+
+         if( fabsf(v3_dot( impulse, player->rb.to_world[2] )) > 10.0f ||
+             fabsf(v3_dot( impulse, player->rb.to_world[1] )) > 50.0f )
+         {
+            /* FIXME */
+#if 0
+            player_kill();
+            return;
+#endif
+         }
+
+         v3_add( impulse, player->rb.v, player->rb.v );
+         v3_cross( delta, impulse, impulse );
+
+         /*
+          * W Impulses are limited to the Y and X axises, we don't really want
+          * roll angular velocities being included.
+          *
+          * Can also tweak the resistance of each axis here by scaling the wx,wy
+          * components.
+          */
+         
+         float wy = v3_dot( player->rb.to_world[1], impulse ) * 0.8f,
+               wx = v3_dot( player->rb.to_world[0], impulse ) * 1.0f;
+
+         v3_muladds( player->rb.w, player->rb.to_world[1], wy, player->rb.w );
+         v3_muladds( player->rb.w, player->rb.to_world[0], wx, player->rb.w );
+      }
+   }
+}
+
+VG_STATIC void skate_integrate( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   /* integrate rigidbody velocities */
+   v3f gravity = { 0.0f, -9.6f, 0.0f };
+   v3_muladds( player->rb.v, gravity, k_rb_delta, player->rb.v );
+   v3_muladds( player->rb.co, player->rb.v, k_rb_delta, player->rb.co );
+
+   float decay_rate = 0.5f*0.125f;
+
+   if( s->state.activity == k_skate_activity_air )
+   {
+      float dist = 1.0f-(s->land_dist/4.0f);
+      decay_rate = 0.5f * vg_maxf( dist*dist, 0.0f );
+   }
+
+   v3_lerp( player->rb.w, (v3f){0.0f,0.0f,0.0f}, decay_rate, player->rb.w );
+
+   if( v3_length2( player->rb.w ) > 0.0f )
+   {
+      v4f rotation;
+      v3f axis;
+      v3_copy( player->rb.w, axis );
+      
+      float mag = v3_length( axis );
+      v3_divs( axis, mag, axis );
+      q_axis_angle( rotation, axis, mag*k_rb_delta );
+      q_mul( rotation, player->rb.q, player->rb.q );
+   }
+
+   /* integrate steering velocities */
+   v4f rotate; 
+   float l = (s->state.activity == k_skate_activity_air)? 0.04f: 0.24f;
+
+   s->state.steery_s = vg_lerpf( s->state.steery_s, s->state.steery, l );
+   s->state.steerx_s = vg_lerpf( s->state.steerx_s, s->state.steerx, l );
+
+   q_axis_angle( rotate, player->rb.to_world[1], s->state.steery_s );
+   q_mul( rotate, player->rb.q, player->rb.q );
+
+   q_axis_angle( rotate, player->rb.to_world[0], s->state.steerx_s );
+   q_mul( rotate, player->rb.q, player->rb.q );
+
+   s->state.steerx = 0.0f;
+   s->state.steery = 0.0f;
+
+   s->state.flip_time += s->state.flip_rate * k_rb_delta;
+   rb_update_transform( &player->rb );
+}
+
+VG_STATIC void player__skate_pre_update( player_instance *player )
+{
+   if( vg_input_button_down( player->input_use ) )
+   {
+      player->subsystem = k_player_subsystem_walk;
+
+      v3f angles;
+      v3_copy( player->cam.angles, angles );
+      angles[2] = 0.0f;
+
+      player__walk_transition( player, angles );
+      return;
+   }
+}
+
+VG_STATIC void player__skate_post_update( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   for( int i=0; i<s->prediction_count; i++ )
+   {
+      struct land_prediction *p = &s->predictions[i];
+      
+      for( int j=0; j<p->log_length - 1; j ++ )
+         vg_line( p->log[j], p->log[j+1], p->colour );
+
+      vg_line_cross( p->log[p->log_length-1], p->colour, 0.25f );
+
+      v3f p1;
+      v3_add( p->log[p->log_length-1], p->n, p1 );
+      vg_line( p->log[p->log_length-1], p1, 0xffffffff );
+
+      vg_line_pt3( p->apex, 0.02f, 0xffffffff );
+   }
+
+   vg_line_pt3( s->state.apex, 0.200f, 0xff0000ff );
+   vg_line_pt3( s->state.apex, 0.201f, 0xff00ffff );
+}
+
+VG_STATIC void player__skate_update( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   v3_copy( player->rb.co, s->state.prev_pos );
+   s->state.activity_prev = s->state.activity;
+
+   /* Setup colliders */
+   m4x3f mtx_front, mtx_back;
+   m3x3_identity( mtx_front );
+   m3x3_identity( mtx_back );
+
+   skate_get_board_points( player, mtx_front[3], mtx_back[3] );
+
+   s->sphere_back.radius = 0.3f;
+   s->sphere_front.radius = 0.3f;
+
+   /* create manifold(s) */
+   rb_ct manifold[72],
+         *interface_manifold = NULL,
+         *grind_manifold = NULL;
+
+   int 
+   len_front = skate_collide_smooth( player, mtx_front, 
+                                     &s->sphere_front, manifold ),
+   len_back = skate_collide_smooth( player, mtx_back,  
+                                    &s->sphere_back, &manifold[len_front] ),
+   interface_len = len_front + len_back;
+
+   /* try to slap both wheels onto the ground when landing to prevent mega 
+    * angular velocities being added */
+   if( (s->state.activity == k_skate_activity_air) && (len_front != len_back) )
+   {
+      v3f trace_from, trace_dir;
+      v3_muls( player->rb.to_world[1], -1.0f, trace_dir );
+
+      if( len_front )
+         v3_copy( mtx_back[3],  trace_from );
+      else
+         v3_copy( mtx_front[3], trace_from );
+
+      ray_hit ray;
+      ray.dist = 0.6f;
+
+      if( ray_world( trace_from, trace_dir, &ray ) )
+      {
+         rb_ct *ct = &manifold[ interface_len ];
+
+         v3_copy( ray.pos, ct->co );
+         v3_copy( ray.normal, ct->n );
+         ct->p = 0.0f;
+
+         interface_len ++;
+      }
+   }
+
+   interface_manifold = manifold;
+   grind_manifold = manifold + interface_len;
+
+   int grind_len = skate_grind_collide( player, grind_manifold );
+
+   for( int i=0; i<interface_len+grind_len; i ++ )
+   {
+      rb_prepare_contact( &manifold[i] );
+      rb_debug_contact( &manifold[i] );
+   }
+
+   skate_apply_grind_model( player, grind_manifold, grind_len );
+   skate_apply_interface_model( player, manifold, interface_len );
+   
+   skate_apply_pump_model( player );
+   skate_apply_cog_model( player );
+   skate_collision_response( player, manifold, interface_len + grind_len );
+
+   skate_apply_grab_model( player );
+   skate_apply_friction_model( player );
+   skate_apply_jump_model( player );
+   skate_apply_air_model( player );
+
+   skate_integrate( player );
+
+   vg_line_pt3( s->state.cog, 0.1f,  VG__WHITE );
+   vg_line_pt3( s->state.cog, 0.11f, VG__WHITE );
+   vg_line_pt3( s->state.cog, 0.12f, VG__WHITE );
+   vg_line_pt3( s->state.cog, 0.13f, VG__WHITE );
+   vg_line_pt3( s->state.cog, 0.14f, VG__WHITE );
+
+   vg_line( player->rb.co, s->state.cog, VG__RED );
+
+
+   teleport_gate *gate;
+   if( (gate = world_intersect_gates( player->rb.co, s->state.prev_pos )) )
+   {
+      m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
+      m3x3_mulv( gate->transport, player->rb.v,  player->rb.v );
+      m4x3_mulv( gate->transport, s->state.cog,   s->state.cog );
+      m3x3_mulv( gate->transport, s->state.cog_v, s->state.cog_v );
+      m3x3_mulv( gate->transport, s->state.throw_v, s->state.throw_v );
+
+      /*camera */
+      m4x3_mulv( gate->transport, s->state.posl, s->state.posl );
+      m3x3_mulv( gate->transport, s->state.vl, s->state.vl );
+      m3x3_mulv( gate->transport, s->state.dirl, s->state.dirl );
+
+#if 0
+      mixedcam_transport( &s->state.cam, gate );
+#endif
+
+      v4f transport_rotation;
+      m3x3_q( gate->transport, transport_rotation );
+      q_mul( transport_rotation, player->rb.q, player->rb.q );
+      rb_update_transform( &player->rb );
+
+      s->state_gate_storage = s->state;
+      player__pass_gate( player, gate );
+   }
+}
+
+VG_STATIC void player__skate_im_gui( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+
+   /* FIXME: Compression */
+   player__debugtext( 1, "V:  %5.2f %5.2f %5.2f",player->rb.v[0],
+                                                player->rb.v[1],
+                                                player->rb.v[2] );
+   player__debugtext( 1, "CO: %5.2f %5.2f %5.2f",player->rb.co[0],
+                                                player->rb.co[1],
+                                                player->rb.co[2] );
+   player__debugtext( 1, "W:  %5.2f %5.2f %5.2f",player->rb.w[0],
+                                                player->rb.w[1],
+                                                player->rb.w[2] );
+
+   player__debugtext( 1, "activity: %s\n",
+                           (const char *[]){ "k_skate_activity_air",
+                                             "k_skate_activity_ground",
+                                             "k_skate_activity_grind }" }
+                                             [s->state.activity] );
+   player__debugtext( 1, "steer_s: %5.2f %5.2f [%.2f %.2f]\n",
+                        s->state.steerx_s, s->state.steery_s,
+                        k_steer_ground, k_steer_air );
+   player__debugtext( 1, "flip: %.4f %.4f\n", s->state.flip_rate, 
+                                             s->state.flip_time );
+}
+
+VG_STATIC void player__skate_animate( player_instance *player,
+                                     player_animation *dest )
+{
+   struct player_skate *s = &player->_skate;
+   struct player_avatar *av = player->playeravatar;
+   struct skeleton *sk = &av->sk;
+
+   /* Head */
+   float kheight = 2.0f,
+         kleg = 0.6f;
+
+   v3f offset;
+   v3_zero( offset );
+
+   m4x3_mulv( player->rb.to_local, s->state.cog, offset );
+   v3_muls( offset, -4.0f, offset );
+
+   static float speed_wobble = 0.0f, speed_wobble_2 = 0.0f;
+
+   float curspeed  = v3_length( player->rb.v ),
+         kickspeed = vg_clampf( curspeed*(1.0f/40.0f), 0.0f, 1.0f ),
+         kicks     = (vg_randf()-0.5f)*2.0f*kickspeed,
+         sign      = vg_signf( kicks );
+
+   s->wobble[0] = vg_lerpf( s->wobble[0], kicks*kicks*sign, 6.0f*vg.time_delta);
+   s->wobble[1] = vg_lerpf( s->wobble[1], speed_wobble,     2.4f*vg.time_delta);
+
+   offset[0] *= 0.26f;
+   offset[0] += speed_wobble_2*3.0f;
+
+   offset[1] *= -0.3f;
+   offset[2] *= 0.01f;
+
+   offset[0]=vg_clampf(offset[0],-0.8f,0.8f)*(1.0f-fabsf(s->blend_slide)*0.9f);
+   offset[1]=vg_clampf(offset[1],-0.5f,0.0f);
+
+   /* 
+    * Animation blending
+    * ===========================================
+    */
+   
+   /* sliding */
+   {
+      float desired  = vg_clampf( fabsf( s->state.slip ), 0.0f, 1.0f );
+      s->blend_slide = vg_lerpf( s->blend_slide, desired, 2.4f*vg.time_delta);
+   }
+   
+   /* movement information */
+   {
+      int iair = (s->state.activity == k_skate_activity_air) ||
+                 (s->state.activity == k_skate_activity_grind );
+
+      float dirz = s->state.reverse > 0.0f? 0.0f: 1.0f,
+            dirx = s->state.slip < 0.0f?    0.0f: 1.0f,
+            fly  = iair?                    1.0f: 0.0f;
+
+      s->blend_z    = vg_lerpf( s->blend_z,   dirz, 2.4f*vg.time_delta );
+      s->blend_x    = vg_lerpf( s->blend_x,   dirx, 0.6f*vg.time_delta );
+      s->blend_fly  = vg_lerpf( s->blend_fly, fly,  2.4f*vg.time_delta );
+   }
+
+   mdl_keyframe apose[32], bpose[32];
+   mdl_keyframe ground_pose[32];
+   {
+      /* when the player is moving fast he will crouch down a little bit */
+      float stand = 1.0f - vg_clampf( curspeed * 0.03f, 0.0f, 1.0f );
+      s->blend_stand = vg_lerpf( s->blend_stand, stand, 6.0f*vg.time_delta );
+
+      /* stand/crouch */
+      float dir_frame   = s->blend_z * (15.0f/30.0f),
+            stand_blend = offset[1]*-2.0f;
+
+      v3f local_cog;
+      m4x3_mulv( player->rb.to_local, s->state.cog, local_cog );
+
+      stand_blend = vg_clampf( 1.0f-local_cog[1], 0, 1 );
+
+      skeleton_sample_anim( sk, s->anim_stand, dir_frame, apose );
+      skeleton_sample_anim( sk, s->anim_highg, dir_frame, bpose );
+      skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
+
+      /* sliding */
+      float slide_frame = s->blend_x * (15.0f/30.0f);
+      skeleton_sample_anim( sk, s->anim_slide, slide_frame, bpose );
+      skeleton_lerp_pose( sk, apose, bpose, s->blend_slide, apose );
+
+      /* pushing */
+      double push_time = vg.time - s->state.start_push;
+      s->blend_push = vg_lerpf( s->blend_push,
+                               (vg.time - s->state.cur_push) < 0.125,
+                               6.0f*vg.time_delta );
+
+      float pt = push_time + vg.accumulator;
+      if( s->state.reverse > 0.0f )
+         skeleton_sample_anim( sk, s->anim_push, pt, bpose );
+      else
+         skeleton_sample_anim( sk, s->anim_push_reverse, pt, bpose );
+
+      skeleton_lerp_pose( sk, apose, bpose, s->blend_push, apose );
+
+      /* trick setup */
+      float jump_start_frame = 14.0f/30.0f;
+
+      float charge = s->state.jump_charge;
+      s->blend_jump = vg_lerpf( s->blend_jump, charge, 8.4f*vg.time_delta );
+
+      float setup_frame = charge * jump_start_frame,
+            setup_blend = vg_minf( s->blend_jump, 1.0f );
+      
+      float jump_frame = (vg.time - s->state.jump_time) + jump_start_frame;
+      if( jump_frame >= jump_start_frame && jump_frame <= (40.0f/30.0f) )
+         setup_frame = jump_frame;
+
+      struct skeleton_anim *jump_anim = s->state.jump_dir?
+                                        s->anim_ollie:
+                                        s->anim_ollie_reverse;
+
+      skeleton_sample_anim_clamped( sk, jump_anim, setup_frame, bpose );
+      skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
+   }
+   
+   mdl_keyframe air_pose[32];
+   {
+      float target = -player->input_js1h->axis.value;
+      s->blend_airdir = vg_lerpf( s->blend_airdir, target, 2.4f*vg.time_delta );
+      
+      float air_frame = (s->blend_airdir*0.5f+0.5f) * (15.0f/30.0f);
+      skeleton_sample_anim( sk, s->anim_air, air_frame, apose );
+
+      static v2f grab_choice;
+
+      v2f grab_input = { player->input_js2h->axis.value,
+                         player->input_js2v->axis.value };
+      v2_add( s->state.grab_mouse_delta, grab_input, grab_input );
+      if( v2_length2( grab_input ) <= 0.001f )
+         grab_input[0] = -1.0f;
+      else
+         v2_normalize_clamp( grab_input );
+      v2_lerp( grab_choice, grab_input, 2.4f*vg.time_delta, grab_choice );
+
+      float ang = atan2f( grab_choice[0], grab_choice[1] ),
+            ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
+            grab_frame = ang_unit * (15.0f/30.0f);
+
+      skeleton_sample_anim( sk, s->anim_grabs, grab_frame, bpose );
+      skeleton_lerp_pose( sk, apose, bpose, s->state.grabbing, air_pose );
+   }
+
+   skeleton_lerp_pose( sk, ground_pose, air_pose, s->blend_fly, dest->pose );
+
+   float add_grab_mod = 1.0f - s->blend_fly;
+
+   /* additive effects */
+   {
+      u32 apply_to[] = { av->id_hip, 
+                         av->id_ik_hand_l,
+                         av->id_ik_hand_r,
+                         av->id_ik_elbow_l,
+                         av->id_ik_elbow_r };
+
+      for( int i=0; i<vg_list_size(apply_to); i ++ )
+      {
+         dest->pose[apply_to[i]-1].co[0] += offset[0]*add_grab_mod;
+         dest->pose[apply_to[i]-1].co[2] += offset[2]*add_grab_mod;
+      }
+
+      mdl_keyframe *kf_board  = &dest->pose[av->id_board-1],
+                   *kf_foot_l = &dest->pose[av->id_ik_foot_l-1],
+                   *kf_foot_r = &dest->pose[av->id_ik_foot_r-1];
+
+      v3f bo;
+      v3_muls( s->board_offset, add_grab_mod, bo );
+
+      v3_add( bo, kf_board->co,  kf_board->co );
+      v3_add( bo, kf_foot_l->co, kf_foot_l->co );
+      v3_add( bo, kf_foot_r->co, kf_foot_r->co );
+
+      m3x3f c;
+      q_m3x3( s->board_rotation, c );
+
+      v3f d;
+      v3_sub( kf_foot_l->co, bo, d );
+      m3x3_mulv( c, d, d );
+      v3_add( bo, d, kf_foot_l->co );
+      
+      v3_sub( kf_foot_r->co, bo, d );
+      m3x3_mulv( c, d, d );
+      v3_add( bo, d, kf_foot_r->co );
+
+      q_mul( s->board_rotation, kf_board->q, kf_board->q );
+      q_normalize( kf_board->q );
+   }
+
+   /* transform */
+   rb_extrapolate( &player->rb, dest->root_co, dest->root_q );
+
+   v3_muladds( dest->root_co, player->rb.to_world[1], -0.28f, dest->root_co );
+
+   v4f qresy, qresx, qresidual;
+   m3x3f mtx_residual;
+   float substep = vg_clampf( vg.accumulator / VG_TIMESTEP_FIXED, 0.0f, 1.0f );
+   q_axis_angle( qresy, player->rb.to_world[1], s->state.steery_s*substep );
+   q_axis_angle( qresx, player->rb.to_world[0], s->state.steerx_s*substep );
+
+   q_mul( qresy, qresx, qresidual );
+   q_normalize( qresidual );
+   q_mul( dest->root_q, qresidual, dest->root_q );
+   q_normalize( dest->root_q );
+
+   v4f qflip;
+   if( (s->state.activity == k_skate_activity_air) &&
+       (fabsf(s->state.flip_rate) > 0.01f) )
+   {
+      float t = s->state.flip_time + s->state.flip_rate*substep*k_rb_delta,
+            angle = vg_clampf( t, -1.0f, 1.0f ) * VG_TAUf,
+            distm = s->land_dist * fabsf(s->state.flip_rate) * 3.0f,
+            blend = vg_clampf( 1.0f-distm, 0.0f, 1.0f );
+
+      angle = vg_lerpf( angle, vg_signf(s->state.flip_rate) * VG_TAUf, blend );
+
+      q_axis_angle( qflip, s->state.flip_axis, angle );
+      q_mul( qflip, dest->root_q, dest->root_q );
+      q_normalize( dest->root_q );
+
+      v3f rotation_point, rco;
+      v3_muladds( player->rb.co, player->rb.to_world[1], 0.5f, rotation_point );
+      v3_sub( dest->root_co, rotation_point, rco );
+      
+      /* FIXME: q_mul v3 */
+      m3x3f TEMP;
+      q_m3x3( qflip, TEMP );
+      m3x3_mulv( TEMP, rco, rco );
+      v3_add( rco, rotation_point, dest->root_co );
+   }
+}
+
+VG_STATIC void skate_camera_vector_look( v3f angles, v3f v, float C, float k )
+{
+   float yaw = atan2f( v[0], -v[2] ),
+       pitch = atan2f
+               ( 
+                   -v[1], 
+                   sqrtf
+                   (
+                     v[0]*v[0] + v[2]*v[2]
+                   )
+               ) * C + k;
+
+   angles[0] = yaw;
+   angles[1] = pitch;
+}
+
+VG_STATIC void skate_camera_firstperson( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   struct player_avatar *av = player->playeravatar;
+
+   /* FIXME: viewpoint entity */
+   v3f vp = {-0.1f,1.8f,0.0f};
+   m4x3_mulv( av->sk.final_mtx[ av->id_head-1 ], vp, player->cam1.co );
+   v3_zero( player->cam1.angles );
+
+   v3f flat_dir,
+       vel_dir,
+       look_dir;
+
+   v3_copy( player->rb.v, vel_dir );
+   //v3_normalize( vel_dir );
+
+   float tti = s->land_dist;
+   v3f   norm;
+   v3_copy( s->land_normal, norm );
+
+   if( s->state.activity == k_skate_activity_ground )
+   {
+      tti = 0.0f;
+      v3_copy( player->rb.to_world[1], norm );
+   }
+
+   v3_muladds( vel_dir, norm, -v3_dot(vel_dir,norm), flat_dir );
+   //v3_normalize( flat_dir );
+
+   v3_lerp( flat_dir, vel_dir, vg_clampf( tti / 2.0f, 0.4f, 1.0f ), look_dir );
+   v3_lerp( s->state.vl, look_dir, 4.0f*vg.time_delta, s->state.vl );
+
+   skate_camera_vector_look( player->cam1.angles, s->state.vl, 1.0f, 0.25f );
+}
+
+VG_STATIC void skate_camera_thirdperson( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   struct player_avatar *av = player->playeravatar;
+
+   v3f origin, dir, target;
+   v3_copy( player->rb.co, origin );
+   v3_add( origin, (v3f){0.0f,1.35f,0.0f}, origin );
+   v3_sub( origin, s->state.posl, dir );
+   
+   if( v3_length2( dir ) < 0.1f*0.1f )
+      v3_copy( (v3f){ 0.0f, 0.0f, 1.0f }, dir );   /* FIXME */
+   else
+      v3_normalize( dir );
+
+   if( s->state.activity == k_skate_activity_air )
+      dir[1] *= vg_maxf( 0.0f, 1.0f - (s->land_dist/2.0f) );
+   dir[1] *= 0.0f;
+
+   v3_muladds( origin, dir, -2.0f, target );
+
+   v3_lerp( s->state.posl, target, vg.frame_delta * 15.0f, s->state.posl );
+   v3_lerp( s->state.dirl, dir, 18.0f*vg.time_delta, s->state.dirl );
+
+   v3_copy( s->state.posl, player->cam3.co );
+   skate_camera_vector_look( player->cam3.angles, s->state.dirl, 1.0f, 0.2f );
+}
+
+VG_STATIC void player__skate_post_animate( player_instance *player )
+{
+   struct player_skate *s = &player->_skate;
+   struct player_avatar *av = player->playeravatar;
+
+   v3_zero( player->cam1.co );
+   v3_zero( player->cam1.angles );
+
+   skate_camera_thirdperson( player );
+   skate_camera_firstperson( player );
+
+   /* FIXME: Organize this. Its int wrong fucking place */
+   v3f vp0 = {0.0f,0.1f, 0.6f},
+       vp1 = {0.0f,0.1f,-0.6f};
+
+   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp0, TEMP_BOARD_0 );
+   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp1, TEMP_BOARD_1 );
+}
+
+VG_STATIC void player__skate_reset( player_instance *player,
+                                   struct respawn_point *rp )
+{
+   struct player_skate *s = &player->_skate;
+   v3_muladds( player->rb.co, player->rb.to_world[1], 1.0f, s->state.cog );
+
+#if 0
+   mixedcam_reset( player, &s->state.cam );
+#endif
+}
+
+VG_STATIC void player__skate_transition( player_instance *player,
+                                         v3f init_velocity )
+{
+   struct player_skate *s = &player->_skate;
+   s->state.activity_prev = k_skate_activity_ground;
+   s->state.activity = k_skate_activity_air;
+
+   v3f dir;
+   v3_copy( init_velocity, dir );
+   v3_normalize( dir );
+
+   q_axis_angle( player->rb.q, (v3f){0.0f,1.0f,0.0f}, 
+                 atan2f( dir[0], dir[2] ) );
+   v3_copy( player->cam.pos, s->state.posl );
+
+   m3x3f temp;
+   euler_m3x3( player->cam.angles, temp );
+   v3_muls( temp[2], -1.0f, s->state.dirl );
+
+   v3_muladds( player->rb.co, player->rb.to_world[1], 1.0f, s->state.cog );
+   v3_copy( init_velocity, s->state.cog_v );
+   v3_copy( init_velocity, player->rb.v );
+
+   rb_update_transform( &player->rb );
+}
+
+
+#endif /* PLAYER_SKATE_C */
diff --git a/player_skate.h b/player_skate.h
new file mode 100644 (file)
index 0000000..57922a6
--- /dev/null
@@ -0,0 +1,121 @@
+#ifndef PLAYER_SKATE_H
+#define PLAYER_SKATE_H
+
+#include "player_api.h"
+
+struct player_skate
+{
+   struct
+   {
+      enum skate_activity
+      {
+         k_skate_activity_air,
+         k_skate_activity_ground,
+         k_skate_activity_grind
+      }
+      activity,
+      activity_prev;
+
+      float steery,
+            steerx,
+            steery_s,
+            steerx_s,
+            reverse,
+            slip;
+
+      v3f   flip_axis;
+      float flip_time,
+            flip_rate;
+
+      m3x3f velocity_bias,
+            velocity_bias_pstep;
+      v3f apex;
+
+      int lift_frames;
+
+      v3f throw_v;
+      v3f cog_v, cog;
+
+      float grabbing;
+      v2f grab_mouse_delta;
+
+      int charging_jump, jump_dir;
+      float jump_charge;
+      double jump_time;
+
+      double start_push,
+             cur_push;
+
+      v3f prev_pos;
+
+      /* FIXME: Sensible names */
+      v3f vl,          /* 1st */
+          posl, dirl;  /* 3rd */
+   }
+   state,
+   state_gate_storage;
+
+   struct land_prediction
+   {
+      v3f   log[50];
+      v3f   n;
+      v3f   apex;
+      u32   log_length;
+      float score,
+            land_dist;
+
+      enum prediction_type
+      {
+         k_prediction_none,
+         k_prediction_land,
+         k_prediction_grind
+      }
+      type;
+
+      u32   colour;
+   }
+   predictions[22];
+   u32 prediction_count;
+   float land_dist;
+   v3f land_normal;
+
+   /* animation */
+   struct skeleton_anim *anim_stand, *anim_highg, *anim_slide,
+                        *anim_air,
+                        *anim_push,  *anim_push_reverse,
+                        *anim_ollie, *anim_ollie_reverse,
+                        *anim_grabs, *anim_stop;
+   rb_sphere sphere_front, sphere_back;
+   v3f board_offset;
+   v4f board_rotation;
+
+   float blend_slide,
+         blend_z,
+         blend_x,
+         blend_fly,
+         blend_stand,
+         blend_push,
+         blend_jump,
+         blend_airdir;
+
+   v2f wobble;
+
+   float debug_normal_pressure;
+   u32 device_id_walk;
+};
+
+VG_STATIC void player__skate_bind         ( player_instance *player );
+VG_STATIC void player__skate_pre_update   ( player_instance *player );
+VG_STATIC void player__skate_update       ( player_instance *player );
+VG_STATIC void player__skate_post_update  ( player_instance *player );
+VG_STATIC void player__skate_im_gui       ( player_instance *player );
+VG_STATIC void player__skate_animate      ( player_instance *player,
+                                            player_animation *anim );
+VG_STATIC void player__skate_post_animate ( player_instance *player );
+VG_STATIC void player__skate_reset        ( player_instance *player,
+                                            struct respawn_point *rp );
+
+VG_STATIC void player__skate_transition   ( player_instance *player,
+                                            v3f init_velocity );
+
+#endif /* PLAYER_SKATE_H */
diff --git a/player_walk.c b/player_walk.c
new file mode 100644 (file)
index 0000000..77386fa
--- /dev/null
@@ -0,0 +1,457 @@
+#ifndef PLAYER_WALK_C
+#define PLAYER_WALK_C
+
+#include "player.h"
+
+VG_STATIC void player__walk_pre_update( player_instance *player )
+{
+   struct player_walk *w = &player->_walk;
+   player_look( player, w->state.angles );
+
+   if( vg_input_button_down( player->input_use ) )
+   {
+      v3f xy_speed, v;
+      v3_copy( player->rb.v, v );
+      v3_copy( v, xy_speed );
+      xy_speed[1] = 0.0f;
+
+      if( v3_length2( xy_speed ) < 0.1f * 0.1f )
+      {
+         v[0] = -sinf( -w->state.angles[0] );
+         v[1] =  0.0f;
+         v[2] = -cosf( -w->state.angles[0] );
+         v3_muls( v, 1.6f, v );
+      }
+
+      player->subsystem = k_player_subsystem_skate;
+      player__skate_transition( player, v );
+      return;
+   }
+}
+
+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_instance *player )
+{
+   struct player_walk *w = &player->_walk;
+   v3_copy( player->rb.co, w->state.prev_pos );
+
+   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 );
+
+   w->move_speed = v2_length( 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 )
+   {
+      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 );
+
+   /* 
+    * CCD routine 
+    * ---------------------------------------------------
+    *
+    */
+   v3f lwr_prev,
+       lwr_now,
+       lwr_offs = { 0.0f, w->collider.radius, 0.0f };
+
+   v3_add( lwr_offs, w->state.prev_pos, lwr_prev );
+   v3_add( lwr_offs, player->rb.co, lwr_now );
+
+   v3f movedelta;
+   v3_sub( player->rb.co, w->state.prev_pos, movedelta );
+
+   float movedist = v3_length( movedelta );
+
+   if( movedist > 0.3f )
+   {
+      float t, sr = w->collider.radius-0.04f;
+      v3f n;
+
+      if( spherecast_world( lwr_prev, lwr_now, sr, &t, n ) != -1 )
+      {
+         v3_lerp( lwr_prev, lwr_now, vg_maxf(0.01f,t), player->rb.co );
+         player->rb.co[1] -= w->collider.radius;
+         rb_update_transform( &player->rb );
+
+         v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
+         debug_capsule( mtx, w->collider.radius, w->collider.height, VG__RED );
+      }
+   }
+
+   teleport_gate *gate;
+   if( (gate = world_intersect_gates( player->rb.co, w->state.prev_pos )) )
+   {
+      m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
+      m3x3_mulv( gate->transport, player->rb.v,  player->rb.v );
+      rb_update_transform( &player->rb );
+
+      /* analytical rotation of yaw */
+      v3f fwd_dir = { cosf(w->state.angles[0]),
+                      0.0f,
+                      sinf(w->state.angles[0])};
+      m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
+      w->state.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
+
+      w->state_gate_storage = w->state;
+      player__pass_gate( player, gate );
+   }
+}
+
+VG_STATIC void player__walk_post_update( player_instance *player )
+{
+   struct player_walk *w = &player->_walk;
+
+   m4x3f mtx;
+   m3x3_identity( mtx );
+   v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
+
+   float substep = vg_clampf( vg.accumulator / k_rb_delta, 0.0f, 1.0f );
+   v3_muladds( mtx[3], player->rb.v, k_rb_delta*substep, mtx[3] );
+   debug_capsule( mtx, w->collider.radius, w->collider.height, VG__YELOW );
+}
+
+VG_STATIC void player__walk_animate( player_instance *player, 
+                                    player_animation *dest )
+{
+   struct player_walk *w = &player->_walk;
+   struct skeleton *sk = &player->playeravatar->sk;
+
+   {
+      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;
+
+      w->blend_fly = vg_lerpf( w->blend_fly, fly, rate*vg.time_delta );
+      w->blend_run = vg_lerpf( w->blend_run,
+                               w->move_speed *
+                               (1.0f + player->input_walk->button.value*0.5f),
+                               2.0f*vg.time_delta );
+   }
+
+   player_pose apose, bpose;
+
+   if( w->move_speed > 0.025f )
+   {
+      /* TODO move */
+      float walk_norm = 30.0f/(float)w->anim_walk->length,
+            run_norm  = 30.0f/(float)w->anim_run->length,
+            walk_adv  = vg_lerpf( walk_norm, run_norm, w->move_speed );
+
+      w->walk_timer += walk_adv * vg.time_delta;
+   }
+   else
+   {
+      w->walk_timer = 0.0f;
+   }
+
+   float walk_norm = (float)w->anim_walk->length/30.0f,
+         run_norm  = (float)w->anim_run->length/30.0f,
+         t = w->walk_timer,
+         l = vg_clampf( w->blend_run*15.0f, 0.0f, 1.0f ),
+         idle_walk = vg_clampf( (w->blend_run-0.1f)/(1.0f-0.1f), 0.0f, 1.0f );
+
+   /* walk/run */
+   skeleton_sample_anim( sk, w->anim_walk, t*walk_norm, apose );
+   skeleton_sample_anim( sk, w->anim_run,  t*run_norm, bpose );
+
+   skeleton_lerp_pose( sk, apose, bpose, l, apose );
+
+   /* idle */
+   skeleton_sample_anim( sk, w->anim_idle, vg.time*0.1f, bpose );
+   skeleton_lerp_pose( sk, apose, bpose, 1.0f-idle_walk, apose );
+
+   /* air */
+   skeleton_sample_anim( sk, w->anim_jump, vg.time*0.6f, bpose );
+   skeleton_lerp_pose( sk, apose, bpose, w->blend_fly, dest->pose );
+
+   /* Create transform */
+   rb_extrapolate( &player->rb, dest->root_co, dest->root_q );
+   q_axis_angle( dest->root_q, (v3f){0.0f,1.0f,0.0f}, 
+                 -w->state.angles[0]-VG_PIf*0.5f );
+}
+
+VG_STATIC void player__walk_post_animate( player_instance *player )
+{
+   /* 
+    * Camera 
+    */
+   struct player_walk *w = &player->_walk;
+   struct player_avatar *av = player->playeravatar;
+
+   /* 3RD */
+   m3x3f angles;
+   euler_m3x3( w->state.angles, angles );
+
+   v3f cast_dir, origin;
+
+   v3_add( player->rb.co, (v3f){0.0f,2.0f,0.0f}, origin );
+
+   v3_muladds( origin, angles[2], 2.0f, player->cam3.co );
+   v3_muladds( player->cam3.co, angles[0], 0.5f, player->cam3.co );
+
+   float t;
+   v3f n;
+   if( spherecast_world( origin, player->cam3.co, 0.1f, &t, n ) != -1 )
+      v3_lerp( origin, player->cam3.co, t, player->cam3.co );
+   v3_copy( w->state.angles, player->cam3.angles );
+
+   /* 1ST */
+   /* FIXME: viewpoint entity */
+   v3f vp = {-0.1f,1.8f,0.0f};
+   m4x3_mulv( av->sk.final_mtx[ av->id_head-1 ], vp, player->cam1.co );
+   v3_copy( w->state.angles, player->cam1.angles );
+
+   /* FIXME: Organize this. Its int wrong fucking place */
+   v3f vp0 = {0.0f,0.1f, 0.6f},
+       vp1 = {0.0f,0.1f,-0.6f};
+
+   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp0, TEMP_BOARD_0 );
+   m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp1, TEMP_BOARD_1 );
+}
+
+
+VG_STATIC void player__walk_im_gui( player_instance *player )
+{
+   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] );
+}
+
+VG_STATIC void player__walk_bind( player_instance *player )
+{
+   struct player_walk *w = &player->_walk;
+   struct player_avatar *av = player->playeravatar;
+   struct skeleton *sk = &av->sk;
+
+   w->anim_idle = skeleton_get_anim( sk, "idle_cycle" );
+   w->anim_walk = skeleton_get_anim( sk, "walk" );
+   w->anim_run  = skeleton_get_anim( sk, "run" );
+   w->anim_jump = skeleton_get_anim( sk, "jump" );
+}
+
+VG_STATIC void player__walk_transition( player_instance *player, v3f angles )
+{
+   struct player_walk *w = &player->_walk;
+   v3_copy( angles, w->state.angles );
+}
+
+#endif /* PLAYER_DEVICE_WALK_H */
diff --git a/player_walk.h b/player_walk.h
new file mode 100644 (file)
index 0000000..7a597c3
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef PLAYER_WALK_H
+#define PLAYER_WALK_H
+
+#include "player_api.h"
+
+struct player_walk
+{
+   rb_capsule collider;
+
+   struct
+   {
+      v3f angles;
+      v3f prev_pos;
+
+      enum walk_activity
+      {
+         k_walk_activity_air,
+         k_walk_activity_ground,
+         k_walk_activity_sleep
+      }
+      activity;
+   }
+   state,
+   state_gate_storage;
+
+   enum mdl_surface_prop surface;
+   struct skeleton_anim *anim_walk, *anim_run, *anim_idle, *anim_jump;
+
+   float blend_fly,
+         blend_run,
+         blend_walk,
+
+         move_speed,
+         walk_timer;
+};
+
+VG_STATIC void player__walk_pre_update  ( player_instance *player );
+VG_STATIC void player__walk_update      ( player_instance *player );
+VG_STATIC void player__walk_post_update ( player_instance *player );
+VG_STATIC void player__walk_animate     ( player_instance *player, 
+                                          player_animation *anim );
+VG_STATIC void player__walk_post_animate( player_instance *player );
+VG_STATIC void player__walk_im_gui      ( player_instance *player );
+VG_STATIC void player__walk_bind        ( player_instance *player );
+VG_STATIC void player__walk_transition  ( player_instance *player, v3f angles );
+
+#endif /* PLAYER_WALK_H */
diff --git a/player_walkgrid.h b/player_walkgrid.h
deleted file mode 100644 (file)
index b2fff82..0000000
+++ /dev/null
@@ -1,967 +0,0 @@
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#ifndef PLAYER_WALKGRID_H
-#define PLAYER_WALKGRID_H
-
-#include "common.h"
-#include "player.h"
-
-/*
- * Walkgrid implementation,
- *  loosely based of cmuratoris youtube video 'Killing the Walkmonster'
- */
-
-#define WALKGRID_SIZE 16
-struct walkgrid
-{
-   struct grid_sample
-   {
-      enum sample_type
-      {
-         k_sample_type_air,      /* Nothing was hit. */
-         k_sample_type_invalid,  /* The point is invalid, but there is a sample
-                                    underneath that can be used */
-         k_sample_type_valid,    /* This point is good */
-      }
-      type;
-
-      v3f clip[2];
-      v3f pos;
-
-      enum traverse_state
-      {
-         k_traverse_none = 0x00,
-         k_traverse_h    = 0x01,
-         k_traverse_v    = 0x02
-      }
-      state;
-   }
-   samples[WALKGRID_SIZE][WALKGRID_SIZE];
-
-   boxf region;
-
-   float move; /* Current amount of movement we have left to apply */
-   v2f dir;    /* The movement delta */
-   v2i cell_id;/* Current cell */
-   v2f pos;    /* Local position (in cell) */
-   float h;
-};
-
-static int player_walkgrid_tri_walkable( u32 tri[3] )
-{
-   return tri[0] > world.sm_geo_std_oob.vertex_count;
-}
-
-/*
- * Get a sample at this pole location, will return 1 if the sample is valid,
- * and pos will be updated to be the intersection location.
- */
-static void player_walkgrid_samplepole( struct grid_sample *s )
-{
-   boxf region = {{ s->pos[0] -0.01f, s->pos[1] - 4.0f, s->pos[2] -0.01f},
-                  { s->pos[0] +0.01f, s->pos[1] + 4.0f, s->pos[2] +0.01f}};
-
-   u32 geo[256];
-   v3f tri[3];
-   int len = bh_select( &world.geo.bhtris, region, geo, 256 );
-   
-   const float k_minworld_y = -2000.0f;
-
-   float walk_height = k_minworld_y,
-         block_height = k_minworld_y;
-
-   s->type = k_sample_type_air;
-
-   for( int i=0; i<len; i++ )
-   {
-      u32 *ptri = &world.geo.indices[ geo[i]*3 ];
-
-      for( int j=0; j<3; j++ )
-         v3_copy( world.geo.verts[ptri[j]].co, tri[j] );
-      
-      v3f vdown = {0.0f,-1.0f,0.0f};
-      v3f sample_from;
-      v3_copy( s->pos, sample_from );
-      sample_from[1] = region[1][1];
-      
-      float dist;
-      if( ray_tri( tri, sample_from, vdown, &dist ))
-      {
-         v3f p0;
-         v3_muladds( sample_from, vdown, dist, p0 );
-
-         if( player_walkgrid_tri_walkable(ptri) )
-         {
-            if( p0[1] > walk_height )
-            {
-               walk_height = p0[1];
-            }
-         }
-         else
-         {
-            if( p0[1] > block_height )
-               block_height = p0[1];
-         }
-      }
-   }
-
-   s->pos[1] = walk_height;
-
-   if( walk_height > k_minworld_y )
-      if( block_height > walk_height )
-         s->type = k_sample_type_invalid;
-      else
-         s->type = k_sample_type_valid;
-   else
-      s->type = k_sample_type_air;
-}
-
-float const k_gridscale = 0.5f;
-
-enum eclipdir
-{
-   k_eclipdir_h = 0,
-   k_eclipdir_v = 1
-};
-
-static void player_walkgrid_clip_blocker( struct grid_sample *sa,
-                                          struct grid_sample *sb,
-                                          struct grid_sample *st,
-                                          enum eclipdir dir )
-{
-   v3f clipdir, pos;
-   int valid_a = sa->type == k_sample_type_valid,
-       valid_b = sb->type == k_sample_type_valid;
-   struct grid_sample *target = valid_a? sa: sb,
-                      *other  = valid_a? sb: sa;
-   v3_copy( target->pos, pos );
-   v3_sub( other->pos, target->pos, clipdir );
-
-   boxf cell_region;
-   v3_muladds( pos, (v3f){1.0f,1.0f,1.0f}, -k_gridscale*2.1f, cell_region[0]);
-   v3_muladds( pos, (v3f){1.0f,1.0f,1.0f},  k_gridscale*2.1f, cell_region[1]);
-
-   u32 geo[256];
-   v3f tri[3];
-   int len = bh_select( &world.geo.bhtris, cell_region, geo, 256 );
-   
-   float start_time = v3_length( clipdir ),
-         min_time = start_time;
-   v3_normalize( clipdir );
-   v3_muls( clipdir, 0.0001f, st->clip[dir] );
-
-   for( int i=0; i<len; i++ )
-   {
-      u32 *ptri = &world.geo.indices[ geo[i]*3 ];
-      for( int j=0; j<3; j++ )
-         v3_copy( world.geo.verts[ptri[j]].co, tri[j] );
-
-      if( player_walkgrid_tri_walkable(ptri) )
-         continue;
-
-      float dist;
-      if(ray_tri( tri, pos, clipdir, &dist ))
-      {
-         if( dist > 0.0f && dist < min_time )
-         {
-            min_time = dist;
-            sb->type = k_sample_type_air;
-         }
-      }
-   }
-
-   if( !(min_time < start_time) )
-      min_time = 0.5f * k_gridscale;
-
-   min_time = vg_clampf( min_time/k_gridscale, 0.01f, 0.99f );
-
-   v3_muls( clipdir, min_time, st->clip[dir] );
-
-   v3f p0;
-   v3_muladds( target->pos, st->clip[dir], k_gridscale, p0 );
-}
-
-static void player_walkgrid_clip_edge( struct grid_sample *sa,
-                                       struct grid_sample *sb,
-                                       struct grid_sample *st, /* data store */
-                                       enum eclipdir dir )
-{
-   v3f clipdir = { 0.0f, 0.0f, 0.0f }, pos;
-   int valid_a = sa->type == k_sample_type_valid,
-       valid_b = sb->type == k_sample_type_valid;
-
-   struct grid_sample *target = valid_a? sa: sb,
-                      *other  = valid_a? sb: sa;
-   
-   v3_sub( other->pos, target->pos, clipdir );
-   clipdir[1] = 0.0f;
-
-   v3_copy( target->pos, pos );
-
-   boxf cell_region;
-   v3_muladds( pos, (v3f){1.0f,1.0f,1.0f}, -k_gridscale*1.1f, cell_region[0]);
-   v3_muladds( pos, (v3f){1.0f,1.0f,1.0f},  k_gridscale*1.1f, cell_region[1]);
-
-   u32 geo[256];
-   int len = bh_select( &world.geo.bhtris, cell_region, geo, 256 );
-
-   float max_dist = 0.0f;
-   v3f tri[3];
-   v3f perp;
-   v3_cross( clipdir,(v3f){0.0f,1.0f,0.0f},perp );
-   v3_muls( clipdir, 0.001f, st->clip[dir] );
-
-   for( int i=0; i<len; i++ )
-   {
-      u32 *ptri = &world.geo.indices[ geo[i]*3 ];
-      for( int j=0; j<3; j++ )
-         v3_copy( world.geo.verts[ptri[j]].co, tri[j] );
-      
-      if( !player_walkgrid_tri_walkable(ptri) )
-         continue;
-
-      for( int k=0; k<3; k++ )
-      {
-         int ia = k,
-             ib = (k+1)%3;
-         
-         v3f v0, v1;
-         v3_sub( tri[ia], pos, v0 );
-         v3_sub( tri[ib], pos, v1 );
-
-         if( (clipdir[2]*v0[0] - clipdir[0]*v0[2]) *
-             (clipdir[2]*v1[0] - clipdir[0]*v1[2]) < 0.0f )
-         {
-            float da = v3_dot(v0,perp),
-                  db = v3_dot(v1,perp),
-                  d  = da-db,
-                  qa = da/d;
-
-            v3f p0;
-            v3_muls( v1, qa, p0 );
-            v3_muladds( p0, v0, 1.0f-qa, p0 );
-            
-            float h = v3_dot(p0,clipdir)/v3_dot(clipdir,clipdir);
-
-            if( h >= max_dist && h <= 1.0f )
-            {
-               max_dist = h;
-               float l = 1.0f/v3_length(clipdir);
-               v3_muls( p0, l, st->clip[dir] );
-            }
-         }
-      }
-   }
-}
-
-static const struct conf
-{
-   struct confedge
-   {  
-      /* i: sample index
-       * d: data index
-       * a: axis index
-       * o: the 'other' point to do a A/B test with
-       *       if its -1, all AB is done.
-       */
-      int i0, i1, 
-          d0, d1,
-          a0, a1,
-          o0, o1;
-   }
-   edges[2];
-   int edge_count;
-}
-k_walkgrid_configs[16] = {
-   {{},0},
-   {{{ 3,3, 3,0, 1,0, -1,-1 }}, 1},
-   {{{ 2,2, 1,3, 0,1, -1,-1 }}, 1},
-   {{{ 2,3, 1,0, 0,0,  3,-1 }}, 1},
-
-   {{{ 1,1, 0,1, 1,0, -1,-1 }}, 1},
-   {{{ 3,3, 3,0, 1,0, -1,-1 },
-     { 1,1, 0,1, 1,0, -1,-1 }}, 2},
-   {{{ 1,2, 0,3, 1,1,  2,-1 }}, 1},
-   {{{ 1,3, 0,0, 1,0,  2, 2 }}, 1},
-
-   {{{ 0,0, 0,0, 0,1, -1,-1 }}, 1},
-   {{{ 3,0, 3,0, 1,1,  0,-1 }}, 1},
-   {{{ 2,2, 1,3, 0,1, -1,-1 },
-     { 0,0, 0,0, 0,1, -1,-1 }}, 2},
-   {{{ 2,0, 1,0, 0,1,  3, 3 }}, 1},
-
-   {{{ 0,1, 0,1, 0,0,  1,-1 }}, 1},
-   {{{ 3,1, 3,1, 1,0,  0, 0 }}, 1},
-   {{{ 0,2, 0,3, 0,1,  1, 1 }}, 1},
-   {{},0},
-};
-
-/* 
- * Get a buffer of edges from cell location
- */
-static const struct conf *player_walkgrid_conf( struct walkgrid *wg, 
-      v2i cell,
-      struct grid_sample *corners[4] )
-{
-   corners[0] = &wg->samples[cell[1]  ][cell[0]  ];
-   corners[1] = &wg->samples[cell[1]+1][cell[0]  ];
-   corners[2] = &wg->samples[cell[1]+1][cell[0]+1];
-   corners[3] = &wg->samples[cell[1]  ][cell[0]+1];
-
-   u32 vd0 = corners[0]->type == k_sample_type_valid,
-       vd1 = corners[1]->type == k_sample_type_valid,
-       vd2 = corners[2]->type == k_sample_type_valid,
-       vd3 = corners[3]->type == k_sample_type_valid,
-       config = (vd0<<3) | (vd1<<2) | (vd2<<1) | vd3;
-
-   return &k_walkgrid_configs[ config ];
-}
-
-static void player_walkgrid_floor(v3f pos)
-{
-   v3_muls( pos, 1.0f/k_gridscale, pos );
-   v3_floor( pos, pos );
-   v3_muls( pos, k_gridscale, pos );
-}
-
-/* 
- * Computes the barycentric coordinate of location on a triangle (vertical),
- * then sets the Y position to the interpolation of the three points
- */
-static void player_walkgrid_stand_tri( v3f a, v3f b, v3f c, v3f pos )
-{
-   v3f v0,v1,v2; 
-   v3_sub( b, a, v0 );
-   v3_sub( c, a, v1 );
-   v3_sub( pos, a, v2 );
-
-   float d = v0[0]*v1[2] - v1[0]*v0[2],
-         v = (v2[0]*v1[2] - v1[0]*v2[2]) / d,
-         w = (v0[0]*v2[2] - v2[0]*v0[2]) / d,
-         u = 1.0f - v - w;
-
-   vg_line( pos, a, 0xffff0000 );
-   vg_line( pos, b, 0xff00ff00 );
-   vg_line( pos, c, 0xff0000ff );
-   pos[1] = u*a[1] + v*b[1] + w*c[1];
-}
-
-/*
- * Get the minimum time value of pos+dir until a cell edge
- *
- * t[0] -> t[3] are the individual time values
- * t[5] & t[6]  are the maximum axis values
- * t[6] is the minimum value
- *
- */
-static void player_walkgrid_min_cell( float t[7], v2f pos, v2f dir )
-{
-   v2f frac = { 1.0f/dir[0], 1.0f/dir[1] };
-
-   t[0] = 999.9f;
-   t[1] = 999.9f;
-   t[2] = 999.9f;
-   t[3] = 999.9f;
-   
-   if( fabsf(dir[0]) > 0.0001f )
-   {
-      t[0] = (0.0f-pos[0]) * frac[0];
-      t[1] = (1.0f-pos[0]) * frac[0];
-   }
-   if( fabsf(dir[1]) > 0.0001f )
-   {
-      t[2] = (0.0f-pos[1]) * frac[1];
-      t[3] = (1.0f-pos[1]) * frac[1];
-   }
-
-   t[4] = vg_maxf(t[0],t[1]);
-   t[5] = vg_maxf(t[2],t[3]);
-   t[6] = vg_minf(t[4],t[5]);
-}
-
-static void player_walkgrid_iter(struct walkgrid *wg, int iter)
-{
-
-   /*
-    * For each walkgrid iteration we are stepping through cells and determining
-    * the intersections with the grid, and any edges that are present
-    */
-
-   u32 icolours[] = { 0xffff00ff, 0xff00ffff, 0xffffff00 };
-   
-   v3f pa, pb, pc, pd, pl0, pl1;
-   pa[0] = wg->region[0][0] + (float)wg->cell_id[0] *k_gridscale;
-   pa[1] = (wg->region[0][1] + wg->region[1][1]) * 0.5f + k_gridscale;
-   pa[2] = wg->region[0][2] + (float)wg->cell_id[1] *k_gridscale;
-#if 0
-   pb[0] = pa[0];
-   pb[1] = pa[1];
-   pb[2] = pa[2] + k_gridscale;
-   pc[0] = pa[0] + k_gridscale;
-   pc[1] = pa[1];
-   pc[2] = pa[2] + k_gridscale;
-   pd[0] = pa[0] + k_gridscale;
-   pd[1] = pa[1];
-   pd[2] = pa[2];
-   /* if you want to draw the current cell */
-   vg_line( pa, pb, 0xff00ffff );
-   vg_line( pb, pc, 0xff00ffff );
-   vg_line( pc, pd, 0xff00ffff );
-   vg_line( pd, pa, 0xff00ffff );
-#endif
-   pl0[0] = pa[0] + wg->pos[0]*k_gridscale;
-   pl0[1] = pa[1];
-   pl0[2] = pa[2] + wg->pos[1]*k_gridscale;
-
-   /* 
-    * If there are edges present, we need to create a 'substep' event, where
-    * we find the intersection point, find the fully resolved position,
-    * then the new pos dir is the intersection->resolution
-    *
-    * the resolution is applied in non-discretized space in order to create a 
-    * suitable vector for finding outflow, we want it to leave the cell so it 
-    * can be used by the quad
-    */
-
-   v2f pos, dir;
-   v2_copy( wg->pos, pos );
-   v2_muls( wg->dir, wg->move, dir );
-
-   struct grid_sample *corners[4];
-   v2f corners2d[4] = {{0.0f,0.0f},{0.0f,1.0f},{1.0f,1.0f},{1.0f,0.0f}};
-   const struct conf *conf = player_walkgrid_conf( wg, wg->cell_id, corners );
-   
-   float t[7];
-   player_walkgrid_min_cell( t, pos, dir );
-
-   for( int i=0; i<conf->edge_count; i++ )
-   {
-      const struct confedge *edge = &conf->edges[i];
-
-      v2f e0, e1, n, r, target, res, tangent;
-      e0[0] = corners2d[edge->i0][0] + corners[edge->d0]->clip[edge->a0][0];
-      e0[1] = corners2d[edge->i0][1] + corners[edge->d0]->clip[edge->a0][2];
-      e1[0] = corners2d[edge->i1][0] + corners[edge->d1]->clip[edge->a1][0];
-      e1[1] = corners2d[edge->i1][1] + corners[edge->d1]->clip[edge->a1][2];
-      
-      v3f pe0 = { pa[0] + e0[0]*k_gridscale,
-                  pa[1],
-                  pa[2] + e0[1]*k_gridscale };
-      v3f pe1 = { pa[0] + e1[0]*k_gridscale,
-                  pa[1],
-                  pa[2] + e1[1]*k_gridscale };
-
-      v2_sub( e1, e0, tangent );
-      n[0] = -tangent[1];
-      n[1] =  tangent[0];
-      v2_normalize( n );
-
-      /*
-       * If we find ourselfs already penetrating the edge, move back out a
-       * little
-       */
-      v2_sub( e0, pos, r );
-      float p1 = v2_dot(r,n);
-
-      if( -p1 < 0.0001f )
-      {
-         v2_muladds( pos, n, p1+0.0001f, pos );
-         v2_copy( pos, wg->pos );
-         v3f p_new = { pa[0] + pos[0]*k_gridscale,
-                       pa[1],
-                       pa[2] + pos[1]*k_gridscale };
-         v3_copy( p_new, pl0 );
-      }
-      
-      v2_add( pos, dir, target );
-
-      v2f v1, v2, v3;
-      v2_sub( e0, pos, v1 );
-      v2_sub( target, pos, v2 );
-
-      v2_copy( n, v3 );
-
-      v2_sub( e0, target, r );
-      float p = v2_dot(r,n),
-            t1 = v2_dot(v1,v3)/v2_dot(v2,v3);
-
-      if( t1 < t[6] && t1 > 0.0f && -p < 0.001f )
-      {
-         v2_muladds( target, n, p+0.0001f, res );
-
-         v2f intersect;
-         v2_muladds( pos, dir, t1, intersect );
-         v2_copy( intersect, pos );
-         v2_sub( res, intersect, dir );
-
-         v3f p_res = { pa[0] + res[0]*k_gridscale,
-                       pa[1],
-                       pa[2] + res[1]*k_gridscale };
-         v3f p_int = { pa[0] + intersect[0]*k_gridscale,
-                       pa[1],
-                       pa[2] + intersect[1]*k_gridscale };
-
-         vg_line( pl0, p_int, icolours[iter%3] );
-         v3_copy( p_int, pl0 );
-         v2_copy( pos, wg->pos );
-
-         player_walkgrid_min_cell( t, pos, dir );
-      }
-   }
-
-   /* 
-    * Compute intersection with grid cell moving outwards
-    */
-   t[6] = vg_minf( t[6], 1.0f );
-   
-   pl1[0] = pl0[0] + dir[0]*k_gridscale*t[6];
-   pl1[1] = pl0[1];
-   pl1[2] = pl0[2] + dir[1]*k_gridscale*t[6];
-   vg_line( pl0, pl1, icolours[iter%3] );
-   
-   if( t[6] < 1.0f )
-   {
-      /*
-       * To figure out what t value created the clip so we know which edge 
-       * to wrap around
-       */
-
-      if( t[4] < t[5] )
-      {
-         wg->pos[1] = pos[1] + dir[1]*t[6];
-
-         if( t[0] > t[1] ) /* left edge */
-         {
-            wg->pos[0] = 0.9999f;
-            wg->cell_id[0] --;
-
-            if( wg->cell_id[0] == 0 )
-               wg->move = -1.0f;
-         }
-         else /* Right edge */
-         {
-            wg->pos[0] = 0.0001f;
-            wg->cell_id[0] ++;
-
-            if( wg->cell_id[0] == WALKGRID_SIZE-2 )
-               wg->move = -1.0f;
-         }
-      }
-      else
-      {
-         wg->pos[0] = pos[0] + dir[0]*t[6];
-
-         if( t[2] > t[3] ) /* bottom edge */
-         {
-            wg->pos[1] = 0.9999f;
-            wg->cell_id[1] --;
-
-            if( wg->cell_id[1] == 0 )
-               wg->move = -1.0f;
-         }
-         else /* top edge */
-         {
-            wg->pos[1] = 0.0001f;
-            wg->cell_id[1] ++;
-
-            if( wg->cell_id[1] == WALKGRID_SIZE-2 )
-               wg->move = -1.0f;
-         }
-      }
-
-      wg->move -= t[6];
-   }
-   else
-   {
-      v2_muladds( wg->pos, dir, wg->move, wg->pos );
-      wg->move = 0.0f;
-   }
-}
-
-static void player_walkgrid_stand_cell(struct walkgrid *wg)
-{
-   /*
-    * NOTE: as opposed to the other function which is done in discretized space
-    *       this use a combination of both.
-    */
-
-   v3f world;
-   world[0] = wg->region[0][0]+((float)wg->cell_id[0]+wg->pos[0])*k_gridscale;
-   world[1] = player.phys.rb.co[1];
-   world[2] = wg->region[0][2]+((float)wg->cell_id[1]+wg->pos[1])*k_gridscale;
-
-   struct grid_sample *corners[4];
-   const struct conf *conf = player_walkgrid_conf( wg, wg->cell_id, corners );
-
-   if( conf != k_walkgrid_configs )
-   {
-      if( conf->edge_count == 0 )
-      {
-         v3f v0;
-
-         /* Split the basic quad along the shortest diagonal */
-         if( fabsf(corners[2]->pos[1] - corners[0]->pos[1]) < 
-             fabsf(corners[3]->pos[1] - corners[1]->pos[1]) )
-         {
-            vg_line( corners[2]->pos, corners[0]->pos, 0xffaaaaaa );
-            
-            if( wg->pos[0] > wg->pos[1] )
-               player_walkgrid_stand_tri( corners[0]->pos,
-                                          corners[3]->pos,
-                                          corners[2]->pos, world );
-            else
-               player_walkgrid_stand_tri( corners[0]->pos,
-                                          corners[2]->pos,
-                                          corners[1]->pos, world );
-         }
-         else
-         {
-            vg_line( corners[3]->pos, corners[1]->pos, 0xffaaaaaa );
-
-            if( wg->pos[0] < 1.0f-wg->pos[1] )
-               player_walkgrid_stand_tri( corners[0]->pos,
-                                          corners[3]->pos,
-                                          corners[1]->pos, world );
-            else
-               player_walkgrid_stand_tri( corners[3]->pos,
-                                          corners[2]->pos,
-                                          corners[1]->pos, world );
-         }
-      }
-      else
-      {
-         for( int i=0; i<conf->edge_count; i++ )
-         {
-            const struct confedge *edge = &conf->edges[i];
-
-            v3f p0, p1;
-            v3_muladds( corners[edge->i0]->pos, 
-                        corners[edge->d0]->clip[edge->a0], k_gridscale, p0 );
-            v3_muladds( corners[edge->i1]->pos, 
-                        corners[edge->d1]->clip[edge->a1], k_gridscale, p1 );
-
-            /* 
-             * Find penetration distance between player position and the edge
-             */
-
-            v2f normal = { -(p1[2]-p0[2]), p1[0]-p0[0] },
-                   rel = { world[0]-p0[0], world[2]-p0[2] };
-
-            if( edge->o0 == -1 )
-            {
-               /* No subregions (default case), just use triangle created by
-                * i0, e0, e1 */
-               player_walkgrid_stand_tri( corners[edge->i0]->pos,
-                                          p0,
-                                          p1, world );
-            }
-            else
-            {
-               /* 
-                * Test if we are in the first region, which is
-                * edge.i0, edge.e0, edge.o0,
-                */
-               v3f v0, ref;
-               v3_sub( p0, corners[edge->o0]->pos, ref );
-               v3_sub( world, corners[edge->o0]->pos, v0 );
-
-               vg_line( corners[edge->o0]->pos, p0, 0xffffff00 );
-               vg_line( corners[edge->o0]->pos, world, 0xff000000 );
-               
-               if( ref[0]*v0[2] - ref[2]*v0[0] < 0.0f )
-               {
-                  player_walkgrid_stand_tri( corners[edge->i0]->pos,
-                                             p0,
-                                             corners[edge->o0]->pos, world );
-               }
-               else
-               {
-                  if( edge->o1 == -1 )
-                  {
-                     /*
-                      * No other edges mean we just need to use the opposite
-                      *
-                      * e0, e1, o0 (in our case, also i1)
-                      */
-                     player_walkgrid_stand_tri( p0,
-                                                p1,
-                                                corners[edge->o0]->pos, world );
-                  }
-                  else
-                  {
-                     /*
-                      * Note: this v0 calculation can be ommited with the 
-                      *       current tileset.
-                      *
-                      *       the last two triangles we have are:
-                      *         e0, e1, o1
-                      *       and
-                      *         e1, i1, o1
-                      */
-                     v3_sub( p1, corners[edge->o1]->pos, ref );
-                     v3_sub( world, corners[edge->o1]->pos, v0 );
-                     vg_line( corners[edge->o1]->pos, p1, 0xff00ffff );
-
-                     if( ref[0]*v0[2] - ref[2]*v0[0] < 0.0f )
-                     {
-                        player_walkgrid_stand_tri( p0,
-                                                   p1,
-                                                   corners[edge->o1]->pos,
-                                                   world );
-                     }
-                     else
-                     {
-                        player_walkgrid_stand_tri( p1,
-                                                   corners[edge->i1]->pos,
-                                                   corners[edge->o1]->pos,
-                                                   world );
-                     }
-                  }
-               }
-            }
-         }
-      }
-   }
-
-   v3_copy( world, player.phys.rb.co );
-}
-
-static void player_walkgrid_getsurface(void)
-{
-   float const k_stepheight = 0.5f;
-   float const k_miny = 0.6f;
-   float const k_height = 1.78f;
-   float const k_region_size = (float)WALKGRID_SIZE/2.0f * k_gridscale;
-
-   static struct walkgrid wg;
-
-   v3f cell;
-   v3_copy( player.phys.rb.co, cell );
-   player_walkgrid_floor( cell );
-
-   v3_muladds( cell, (v3f){-1.0f,-1.0f,-1.0f}, k_region_size, wg.region[0] );
-   v3_muladds( cell, (v3f){ 1.0f, 1.0f, 1.0f}, k_region_size, wg.region[1] );
-
-   
-   /* 
-    * Create player input vector
-    */
-   v3f delta = {0.0f,0.0f,0.0f};
-   v3f fwd = { -sinf(-player.angles[0]), 0.0f, -cosf(-player.angles[0]) },
-       side = { -fwd[2], 0.0f, fwd[0] };
-
-   /* Temp */
-   if( !vg_console_enabled() )
-   {
-      if( glfwGetKey( vg.window, GLFW_KEY_W ) )
-         v3_muladds( delta, fwd,  ktimestep*k_walkspeed, delta );
-      if( glfwGetKey( vg.window, GLFW_KEY_S ) )
-         v3_muladds( delta, fwd, -ktimestep*k_walkspeed, delta );
-
-      if( glfwGetKey( vg.window, GLFW_KEY_A ) )
-         v3_muladds( delta, side, -ktimestep*k_walkspeed, delta );
-      if( glfwGetKey( vg.window, GLFW_KEY_D ) )
-         v3_muladds( delta, side,  ktimestep*k_walkspeed, delta );
-
-      v3_muladds( delta, fwd, 
-            vg_get_axis("vertical")*-ktimestep*k_walkspeed, delta );
-      v3_muladds( delta, side, 
-            vg_get_axis("horizontal")*ktimestep*k_walkspeed, delta );
-   }
-
-   /* 
-    * Create our move in grid space
-    */
-   wg.dir[0] = delta[0] * (1.0f/k_gridscale);
-   wg.dir[1] = delta[2] * (1.0f/k_gridscale);
-   wg.move = 1.0f;
-
-   v2f region_pos = 
-   {
-      (player.phys.rb.co[0] - wg.region[0][0]) * (1.0f/k_gridscale),
-      (player.phys.rb.co[2] - wg.region[0][2]) * (1.0f/k_gridscale)
-   };
-   v2f region_cell_pos;
-   v2_floor( region_pos, region_cell_pos );
-   v2_sub( region_pos, region_cell_pos, wg.pos );
-
-   wg.cell_id[0] = region_cell_pos[0];
-   wg.cell_id[1] = region_cell_pos[1];
-
-   for(int y=0; y<WALKGRID_SIZE; y++ )
-   {
-      for(int x=0; x<WALKGRID_SIZE; x++ )
-      {
-         struct grid_sample *s = &wg.samples[y][x];
-         v3_muladds( wg.region[0], (v3f){ x, 0, y }, k_gridscale, s->pos );
-         s->state = k_traverse_none;
-         s->type = k_sample_type_air;
-         v3_zero( s->clip[0] );
-         v3_zero( s->clip[1] );
-      }
-   }
-
-   v2i border[WALKGRID_SIZE*WALKGRID_SIZE];
-   v2i *cborder = border;
-   u32 border_length = 1;
-
-   struct grid_sample *base = NULL; 
-
-   v2i starters[] = {{0,0},{1,1},{0,1},{1,0}};
-
-   for( int i=0;i<4;i++ )
-   {
-      v2i test;
-      v2i_add( wg.cell_id, starters[i], test );
-      v2i_copy( test, border[0] );
-      base = &wg.samples[test[1]][test[0]];
-
-      base->pos[1] = cell[1];
-      player_walkgrid_samplepole( base );
-
-      if( base->type == k_sample_type_valid )
-         break;
-      else
-         base->type = k_sample_type_air;
-   }
-   
-   vg_line_pt3( base->pos, 0.1f, 0xffffffff );
-
-   int iter = 0;
-
-   while( border_length )
-   {
-      v2i directions[] = {{1,0},{0,1},{-1,0},{0,-1}};
-
-      v2i *old_border = cborder;
-      int  len = border_length;
-
-      border_length = 0;
-      cborder = old_border+len;
-
-      for( int i=0; i<len; i++ )
-      {
-         v2i co;
-         v2i_copy( old_border[i], co );
-         struct grid_sample *sa = &wg.samples[co[1]][co[0]];
-
-         for( int j=0; j<4; j++ )
-         {
-            v2i newp;
-            v2i_add( co, directions[j], newp );
-
-            if( newp[0] < 0 || newp[1] < 0 || 
-                newp[0] == WALKGRID_SIZE || newp[1] == WALKGRID_SIZE )
-               continue;
-
-            struct grid_sample *sb = &wg.samples[newp[1]][newp[0]];
-            enum traverse_state thismove = j%2==0? 1: 2;
-
-            if( (sb->state & thismove) == 0x00 || 
-                  sb->type == k_sample_type_air )
-            {
-               sb->pos[1] = sa->pos[1];
-
-               player_walkgrid_samplepole( sb );
-
-               if( sb->type != k_sample_type_air )
-               {
-                  /* 
-                   * Need to do a blocker pass
-                   */
-
-                  struct grid_sample *store = (j>>1 == 0)? sa: sb;
-                  player_walkgrid_clip_blocker( sa, sb, store, j%2 );
-                  
-
-                  if( sb->type != k_sample_type_air )
-                  {
-                     vg_line( sa->pos, sb->pos, 0xffffffff );
-                  
-                     if( sb->state == k_traverse_none )
-                        v2i_copy( newp, cborder[ border_length ++ ] );
-                  }
-                  else
-                  {
-                     v3f p1;
-                     v3_muladds( sa->pos, store->clip[j%2], k_gridscale, p1 );
-                     vg_line( sa->pos, p1, 0xffffffff );
-                  }
-               }
-               else
-               {
-                  /* 
-                   * A clipping pass is now done on the edge of the walkable
-                   * surface
-                   */
-                  
-                  struct grid_sample *store = (j>>1 == 0)? sa: sb;
-                  player_walkgrid_clip_edge( sa, sb, store, j%2 );
-
-                  v3f p1;
-                  v3_muladds( sa->pos, store->clip[j%2], k_gridscale, p1 );
-                  vg_line( sa->pos, p1, 0xffffffff );
-               }
-
-               sb->state |= thismove;
-            }
-         }
-
-         sa->state = k_traverse_h|k_traverse_v;
-      }
-
-      iter ++;
-      if( iter == walk_grid_iterations )
-         break;
-   }
-
-   /* Draw connections */
-   struct grid_sample *corners[4];
-   for( int x=0; x<WALKGRID_SIZE-1; x++ )
-   {
-      for( int z=0; z<WALKGRID_SIZE-1; z++ )
-      {
-         const struct conf *conf = 
-            player_walkgrid_conf( &wg, (v2i){x,z}, corners );
-
-         for( int i=0; i<conf->edge_count; i++ )
-         {
-            const struct confedge *edge = &conf->edges[i];
-
-            v3f p0, p1;
-            v3_muladds( corners[edge->i0]->pos, 
-                    corners[edge->d0]->clip[edge->a0], k_gridscale, p0 );
-            v3_muladds( corners[edge->i1]->pos, 
-                    corners[edge->d1]->clip[edge->a1], k_gridscale, p1 );
-
-            vg_line( p0, p1, 0xff0000ff );
-         }
-      }
-   }
-
-   /*
-    * Commit player movement into the grid 
-    */
-   
-   if( v3_length2(delta) <= 0.00001f )
-      return;
-   
-   int i=0;
-   for(; i<8 && wg.move > 0.001f; i++ )
-      player_walkgrid_iter( &wg, i );
-
-   player_walkgrid_stand_cell( &wg );
-}
-
-static void player_walkgrid(void)
-{
-   player_walkgrid_getsurface();
-   
-   m4x3_mulv(player.phys.rb.to_world, (v3f){0.0f,1.8f,0.0f}, player.camera_pos);
-   player_mouseview();
-   rb_update_transform( &player.phys.rb );
-}
-
-#endif /* PLAYER_WALKGRID_H */
index b3d51c364653e637fba9df4ec5f44f6db53f2d34..574f3a6bc73e706b6048365741f5134a9dd23790 100644 (file)
 #if 0
 #include "player.h"
 #else
+
+#include "player.h"
+VG_STATIC player_instance localplayer;
+
+#if 0
+
 #include "player_interface.h"
 #include "player_device_walk.h"
 #include "player_device_skate.h"
 #include "player_device_dead.h"
 #include "player_model.h"
 
+
 /* temp */
 VG_STATIC player_interface localplayer;
+#endif
+
 VG_STATIC struct player_avatar localplayer_avatar;
 VG_STATIC glmesh localplayer_meshes[3];
+vg_tex2d localplayer_texture = { .path = "textures/ch_gradient.qoi" };
 
 #endif
 
@@ -121,7 +131,7 @@ VG_STATIC int __respawn( int argc, const char *argv[] )
       rp = &world.spawns[0];
    }
 
-   player_spawn( &localplayer, rp );
+   player__spawn( &localplayer, rp );
    return 1;
 }
 
@@ -225,7 +235,7 @@ VG_STATIC void load_playermodels(void)
    shader_viewchar_register();
    vg_acquire_thread_sync();
    {
-      vg_tex2d_init( (vg_tex2d *[]){ &tex_characters }, 1 );
+      vg_tex2d_init( (vg_tex2d *[]){ &localplayer_texture }, 1 );
    }
    vg_release_thread_sync();
 }
@@ -244,16 +254,12 @@ VG_STATIC void vg_load(void)
    vg_loader_step( load_playermodels, NULL );
   
    /* player setup */
-   player_interface_create_player( &localplayer );
-   
+   player__create( &localplayer );
    player_avatar_load( &localplayer_avatar, "models/ch_new.mdl" );
-   player_use_avatar( &localplayer, &localplayer_avatar );
-   player_use_mesh( &localplayer, &localplayer_meshes[0] );
-
-   player_add_device( &localplayer, &player_device_walk );
-   player_add_device( &localplayer, &player_device_skate );
-   player_add_device( &localplayer, &player_device_dead );
-   player_bind( &localplayer );
+   player__use_avatar( &localplayer, &localplayer_avatar );
+   player__use_mesh( &localplayer, &localplayer_meshes[0] );
+   player__use_texture( &localplayer, &localplayer_texture );
+   player__bind( &localplayer );
 
    /* --------------------- */
 
@@ -294,7 +300,7 @@ VG_STATIC void vg_update(void)
          player_update_pre();
 #endif
 
-      player_pre_update( &localplayer );
+      player__pre_update( &localplayer );
       world_update( localplayer.rb.co );
    }
 }
@@ -310,7 +316,7 @@ VG_STATIC void vg_update_fixed(void)
       vehicle_update_fixed();
 #endif
 
-      player_update( &localplayer );
+      player__update( &localplayer );
    }
 }
 
@@ -329,7 +335,7 @@ VG_STATIC void vg_update_post(void)
       }
 #endif
 
-      player_post_update( &localplayer );
+      player__post_update( &localplayer );
 
 #if 0
       menu_update();
@@ -400,7 +406,7 @@ VG_STATIC void render_player_transparent(void)
 
    /* Draw player to window buffer and blend background ontop */
    glBindFramebuffer( GL_FRAMEBUFFER, 0 );
-   player_render( &small_cam, &localplayer );
+   player__render( &small_cam, &localplayer );
 }
 
 VG_STATIC void render_scene(void)
@@ -419,7 +425,7 @@ VG_STATIC void render_scene(void)
        player_draw        = 1;
 
    if( !player_transparent && player_draw )
-      player_render( &main_camera, &localplayer );
+      player__render( &main_camera, &localplayer );
 
    render_water_texture( &main_camera );
    render_fb_bind( gpipeline.fb_main );
@@ -459,7 +465,7 @@ VG_STATIC void render_main_game(void)
     * TODO: blend with camera from menu */
 
    /* FIXME: TEMP!! */
-   player_pre_render( &localplayer );
+   player__pre_render( &localplayer );
 
    v3_copy( localplayer.cam.pos, main_camera.pos );
    v3_copy( localplayer.cam.angles, main_camera.angles );
@@ -510,7 +516,7 @@ 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 );
+   player__im_gui( &localplayer );
 
 #if 0
    menu_crap_ui();