X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=player_device_skate.h;fp=player_device_skate.h;h=af95a7ff1a4a0531d855f9e835290d31afeb4836;hb=38672ec938499ce9e610c3e1848e74880b78a845;hp=0000000000000000000000000000000000000000;hpb=07d0834b13ecf5de312cf3578e8aca8f106f5ef1;p=carveJwlIkooP6JGAAIwe30JlM.git diff --git a/player_device_skate.h b/player_device_skate.h new file mode 100644 index 0000000..af95a7f --- /dev/null +++ b/player_device_skate.h @@ -0,0 +1,1458 @@ +#ifndef PLAYER_DEVICE_SKATE_H +#define PLAYER_DEVICE_SKATE_H + +#include "player_interface.h" +#include "skeleton.h" +#include "player_model.h" + +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; + + m3x3f velocity_bias, + velocity_bias_pstep; + + 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 vl; + } + state, + state_gate_storage; + + 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; + + /* 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; +}; + +VG_STATIC void player_skate_bind( player_interface *player, + player_attachment *at ) +{ + struct player_device_skate *s = at->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" ); +} + +VG_STATIC void player_skate_pre_update( player_interface *player, + player_attachment *at ) +{ +} + +/* + * 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; irb; + 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_interface *player, + player_attachment *at, 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; + + prediction->log_length = 0; + + for( int i=0; ilog); 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 = 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 ++ ] ); + 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; + } +} + +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; + + /* + * 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; + } + + 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; iprediction_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; + } +} + +/* + * + * 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)*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 }; + 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; +} + +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 = skate_simulate_spring( player, s, spring0 ), + spring_hit1 = 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 ); + + /* Surface connection */ + if( len == 0 && !(spring_hit0 && spring_hit1) ) + { + s->state.lift_frames ++; + + if( s->state.lift_frames >= 8 ) + s->state.activity = k_skate_activity_air; + } + else + { + v3f surface_avg; + v3_zero( surface_avg ); + + for( int i=0; irb.v, surface_avg ) > 0.7f ) + { + s->state.lift_frames ++; + + if( s->state.lift_frames >= 8 ) + 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; + + /* 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; ico, 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 ); + + v3_lerp( player->rb.w, (v3f){0.0f,0.0f,0.0f}, 0.125f*0.5f, 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.3f; + + 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 + + rb_update_transform( &player->rb ); +} + +VG_STATIC void player_skate_update( player_interface *player, + player_attachment *at ) +{ + struct player_device_skate *s = at->storage; + 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; + + interface_manifold = manifold; + grind_manifold = manifold + interface_len; + + int grind_len = skate_grind_collide( player, at, grind_manifold ); + + for( int i=0; istorage; + + /* 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] ); +} + +VG_STATIC void player_skate_pose( player_interface *player, + player_attachment *at, + player_pose pose, m4x3f transform ) +{ + struct player_device_skate *s = at->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 ); + +#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, 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; iid_board-1], + *kf_foot_l = &pose[av->id_ik_foot_l-1], + *kf_foot_r = &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_transform( &player->rb, transform ); + +#if 0 + v3_muladds( player.visual_transform[3], phys->rb.up, -0.2f, + player.visual_transform[3] ); +#endif + + 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_m3x3( qresidual, mtx_residual ); + m3x3_mul( transform, mtx_residual, transform ); +} + +VG_STATIC void player_skate_get_camera( player_interface *player, + player_attachment *at, camera *cam ) +{ + struct player_device_skate *s = at->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, cam->pos ); + + v3_zero( cam->angles ); + cam->fov = 119.0f; + + /* TODO: smooth clamp lerp rate of change */ + + v3_lerp( s->state.vl, player->rb.v, 5.0f*vg.time_delta, s->state.vl ); + + float *v = s->state.vl, + yaw = atan2f( v[0], -v[2] ), + pitch = atan2f + ( + -v[1], + sqrtf + ( + v[0]*v[0] + v[2]*v[2] + ) + ) + * 0.7f + 0.5f; + + cam->angles[0] = yaw; + cam->angles[1] = pitch; +} + +VG_STATIC void player_skate_transport( player_interface *player, + player_attachment *at, + teleport_gate *gate ) +{ + struct player_device_skate *s = at->storage; + + m4x3_mulv( gate->transport, player->rb.co, player->rb.co ); + 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, player->rb.v, player->rb.v ); + m3x3_mulv( gate->transport, s->state.vl, s->state.vl ); + + v4f transport_rotation; + m3x3_q( gate->transport, transport_rotation ); + q_mul( transport_rotation, player->rb.q, player->rb.q ); + + s->state_gate_storage = s->state; +} + +VG_STATIC player_device player_device_skate = +{ + .pre_update = player_skate_pre_update, + .update = player_skate_update, + .post_update = player_skate_post_update, + .get_camera = player_skate_get_camera, + .debug_ui = player_skate_ui, + .bind = player_skate_bind, + .pose = player_skate_pose, + .gate_transport= player_skate_transport +}; + +#endif /* PLAYER_DEVICE_SKATE_H */