+
+VG_STATIC void skate_integrate( player_instance *player )
+{
+ struct player_skate *s = &player->_skate;
+
+ float decay_rate_x = 1.0f - (k_rb_delta * 3.0f),
+ decay_rate_z = decay_rate_x,
+ decay_rate_y = 1.0f;
+
+ if( s->state.activity >= k_skate_activity_grind_any ){
+#if 0
+ decay_rate = 1.0f-vg_lerpf( 3.0f, 20.0f, s->grind_strength ) * k_rb_delta;
+ decay_rate_y = decay_rate;
+#endif
+ decay_rate_x = 1.0f-(16.0f*k_rb_delta);
+ decay_rate_y = 1.0f-(10.0f*k_rb_delta);
+ decay_rate_z = 1.0f-(40.0f*k_rb_delta);
+ }
+
+ float wx = v3_dot( player->rb.w, player->rb.to_world[0] ) * decay_rate_x,
+ wy = v3_dot( player->rb.w, player->rb.to_world[1] ) * decay_rate_y,
+ wz = v3_dot( player->rb.w, player->rb.to_world[2] ) * decay_rate_z;
+
+ v3_muls( player->rb.to_world[0], wx, player->rb.w );
+ v3_muladds( player->rb.w, player->rb.to_world[1], wy, player->rb.w );
+ v3_muladds( player->rb.w, player->rb.to_world[2], wz, player->rb.w );
+
+ s->state.flip_time += s->state.flip_rate * k_rb_delta;
+ rb_update_transform( &player->rb );
+}
+
+/*
+ * 1 2 or 3
+ */
+
+VG_STATIC void skate_copy_holdout( player_instance *player )
+{
+ struct player_skate *s = &player->_skate;
+ struct player_avatar *av = player->playeravatar;
+ struct skeleton *sk = &av->sk;
+ skeleton_copy_pose( sk, s->holdout, player->holdout_pose );
+}
+
+VG_STATIC int player_skate_trick_input( player_instance *player )
+{
+ return (button_press( k_srbind_trick0 ) ) |
+ (button_press( k_srbind_trick1 ) << 1) |
+ (button_press( k_srbind_trick2 ) << 1) |
+ (button_press( k_srbind_trick2 ) );
+}
+
+VG_STATIC void player__skate_pre_update( player_instance *player )
+{
+ struct player_skate *s = &player->_skate;
+
+ if( button_down( k_srbind_use ) ){
+ player->subsystem = k_player_subsystem_walk;
+
+ v3f angles;
+ v3_copy( player->cam.angles, angles );
+ angles[2] = 0.0f;
+
+ skate_copy_holdout( player );
+ player->holdout_time = 0.34f;
+ player__skate_kill_audio( player );
+ player__walk_transition( player, angles );
+ return;
+ }
+
+ int trick_id;
+ if( (s->state.activity <= k_skate_activity_air_to_grind) &&
+ (trick_id = player_skate_trick_input( player )) )
+ {
+ if( (vg.time - s->state.jump_time) < 0.1f ){
+ v3_zero( s->state.trick_vel );
+ s->state.trick_time = 0.0f;
+
+ if( trick_id == 1 ){
+ s->state.trick_vel[0] = 3.0f;
+ }
+ else if( trick_id == 2 ){
+ s->state.trick_vel[2] = 3.0f;
+ }
+ else if( trick_id == 3 ){
+ s->state.trick_vel[0] = 2.0f;
+ s->state.trick_vel[2] = 2.0f;
+ }
+ }
+ }
+}
+
+VG_STATIC void player__skate_post_update( player_instance *player )
+{
+ struct player_skate *s = &player->_skate;
+
+ for( int i=0; i<s->possible_jump_count; i++ ){
+ jump_info *jump = &s->possible_jumps[i];
+
+ if( jump->log_length == 0 ){
+ vg_fatal_error( "assert: jump->log_length == 0\n" );
+ }
+
+ for( int j=0; j<jump->log_length - 1; j ++ ){
+ float brightness = jump->score*jump->score*jump->score;
+ v3f p1;
+ v3_lerp( jump->log[j], jump->log[j+1], brightness, p1 );
+ vg_line( jump->log[j], p1, jump->colour );
+ }
+
+ vg_line_cross( jump->log[jump->log_length-1], jump->colour, 0.25f );
+
+ v3f p1;
+ v3_add( jump->log[jump->log_length-1], jump->n, p1 );
+ vg_line( jump->log[jump->log_length-1], p1, 0xffffffff );
+
+ vg_line_pt3( jump->apex, 0.02f, 0xffffffff );
+ }
+
+ audio_lock();
+
+ float air = s->state.activity <= k_skate_activity_air_to_grind? 1.0f: 0.0f,
+ speed = v3_length( player->rb.v ),
+ attn = vg_minf( 1.0f, speed*0.1f ),
+ slide = vg_clampf( fabsf(s->state.slip), 0.0f, 1.0f );
+
+ if( s->state.activity >= k_skate_activity_grind_any ){
+ slide = 0.0f;
+ }
+
+ f32 gate = 1.0f-menu.factive,
+ vol_main = sqrtf( (1.0f-air)*attn*(1.0f-slide) * 0.4f ) * gate,
+ vol_air = sqrtf( air *attn * 0.5f ) * gate,
+ vol_slide = sqrtf( (1.0f-air)*attn*slide * 0.25f ) * gate;
+
+ const u32 flags = AUDIO_FLAG_SPACIAL_3D|AUDIO_FLAG_LOOP;
+
+ if( !s->aud_air ){
+ s->aud_air = audio_get_first_idle_channel();
+ if( s->aud_air )
+ audio_channel_init( s->aud_air, &audio_board[1], flags );
+ }
+
+ if( !s->aud_slide ){
+ s->aud_slide = audio_get_first_idle_channel();
+ if( s->aud_slide )
+ audio_channel_init( s->aud_slide, &audio_board[2], flags );
+ }
+
+
+ /* brrrrrrrrrrrt sound for tiles and stuff
+ * --------------------------------------------------------*/
+ float sidechain_amt = 0.0f,
+ hz = vg_maxf( speed * 2.0f, 2.0f );
+
+ if( (s->surface == k_surface_prop_tiles) &&
+ (s->state.activity < k_skate_activity_grind_any) )
+ sidechain_amt = 1.0f;
+ else
+ sidechain_amt = 0.0f;
+
+ audio_set_lfo_frequency( 0, hz );
+ audio_set_lfo_wave( 0, k_lfo_polynomial_bipolar,
+ vg_lerpf( 250.0f, 80.0f, attn ) );
+
+ if( s->sample_change_cooldown > 0.0f ){
+ s->sample_change_cooldown -= vg.time_frame_delta;
+ }
+ else{
+ int sample_type = k_skate_sample_concrete;
+
+ if( s->state.activity == k_skate_activity_grind_5050 ){
+ if( s->surface == k_surface_prop_metal )
+ sample_type = k_skate_sample_metal_scrape_generic;
+ else
+ sample_type = k_skate_sample_concrete_scrape_metal;
+ }
+ else if( (s->state.activity == k_skate_activity_grind_back50) ||
+ (s->state.activity == k_skate_activity_grind_front50) )
+ {
+ if( s->surface == k_surface_prop_metal ){
+ sample_type = k_skate_sample_metal_scrape_generic;
+ }
+ else{
+ float a = v3_dot( player->rb.to_world[2], s->grind_dir );
+ if( fabsf(a) > 0.70710678118654752f )
+ sample_type = k_skate_sample_concrete_scrape_wood;
+ else
+ sample_type = k_skate_sample_concrete_scrape_metal;
+ }
+ }
+ else if( s->state.activity == k_skate_activity_grind_boardslide ){
+ if( s->surface == k_surface_prop_metal )
+ sample_type = k_skate_sample_metal_scrape_generic;
+ else
+ sample_type = k_skate_sample_concrete_scrape_wood;
+ }
+
+ audio_clip *relevant_samples[] = {
+ &audio_board[0],
+ &audio_board[0],
+ &audio_board[7],
+ &audio_board[6],
+ &audio_board[5]
+ };
+
+ if( (s->main_sample_type != sample_type) || (!s->aud_main) ){
+ s->aud_main =
+ audio_channel_crossfade( s->aud_main, relevant_samples[sample_type],
+ 0.06f, flags );
+ s->sample_change_cooldown = 0.1f;
+ s->main_sample_type = sample_type;
+ }
+ }
+
+ if( s->aud_main ){
+ s->aud_main->colour = 0x00103efe;
+ audio_channel_set_spacial( s->aud_main, player->rb.co, 40.0f );
+ //audio_channel_slope_volume( s->aud_main, 0.05f, vol_main );
+ audio_channel_edit_volume( s->aud_main, vol_main, 1 );
+ audio_channel_sidechain_lfo( s->aud_main, 0, sidechain_amt );
+
+ float rate = 1.0f + (attn-0.5f)*0.2f;
+ audio_channel_set_sampling_rate( s->aud_main, rate );
+ }
+
+ if( s->aud_slide ){
+ s->aud_slide->colour = 0x00103efe;
+ audio_channel_set_spacial( s->aud_slide, player->rb.co, 40.0f );
+ //audio_channel_slope_volume( s->aud_slide, 0.05f, vol_slide );
+ audio_channel_edit_volume( s->aud_slide, vol_slide, 1 );
+ audio_channel_sidechain_lfo( s->aud_slide, 0, sidechain_amt );
+ }
+
+ if( s->aud_air ){
+ s->aud_air->colour = 0x00103efe;
+ audio_channel_set_spacial( s->aud_air, player->rb.co, 40.0f );
+ //audio_channel_slope_volume( s->aud_air, 0.05f, vol_air );
+ audio_channel_edit_volume( s->aud_air, vol_air, 1 );
+ }
+
+ audio_unlock();
+}
+
+/*
+ * truck alignment model at ra(local)
+ * returns 1 if valid surface:
+ * surface_normal will be filled out with an averaged normal vector
+ * axel_dir will be the direction from left to right wheels
+ *
+ * returns 0 if no good surface found
+ */
+VG_STATIC
+int skate_compute_surface_alignment( player_instance *player,
+ v3f ra, u32 colour,
+ v3f surface_normal, v3f axel_dir )