X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=player_physics_skate.h;fp=player_physics_skate.h;h=f4b960fe480195946ff055ded49d7e5953a73d00;hb=2ab1c45f664daf5a452fd212c89dcfd918f7dd81;hp=0000000000000000000000000000000000000000;hpb=d45f2b7d71311ce5ce8cd3496844b4ec7d2f46ac;p=carveJwlIkooP6JGAAIwe30JlM.git diff --git a/player_physics_skate.h b/player_physics_skate.h new file mode 100644 index 0000000..f4b960f --- /dev/null +++ b/player_physics_skate.h @@ -0,0 +1,1117 @@ +/* + * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ + +#ifndef PLAYER_PHYSICS_SKATE_H +#define PLAYER_PHYSICS_SKATE_H + +#include "player.h" +#include "camera.h" + +VG_STATIC struct player_skate +{ + struct skate_phys + { + enum skate_activity + { + k_skate_activity_air, + k_skate_activity_ground, + k_skate_activity_grind + } + activity, + activity_prev; + + v3f v_prev, a, m, bob, vl; /* TODO: please clarify */ + + float iY, siY; /* Yaw inertia */ + + float vswitch, slip, slip_last, reverse; + float grab, jump; + v2f grab_mouse_delta; + + v3f throw_v; + v3f cog, + cog_v; + + int lift_frames; + + double start_push, + cur_push; + + int charging_jump, jump_dir; + + m3x3f vr,vr_pstep; + } + phys, + phys_frame; + + float normal_pressure; + v3f debug_mmcollect_lat, + debug_mmcollect_vert; + + struct land_prediction + { + v3f log[50]; + v3f n; + u32 log_length; + float score; + + enum prediction_type + { + k_prediction_none, + k_prediction_land, + k_prediction_grind + } + type; + + u32 colour; + } + predictions[22]; + u32 prediction_count; + + rigidbody rbf, rbb; +} +player_skater; + +/* + * Gets the closest grindable edge to the player within max_dist + */ +VG_STATIC struct grind_edge *player_collect_grind_edge( v3f p0, v3f p1, + v3f c0, v3f c1, + float max_dist ) +{ + bh_iter it; + bh_iter_init( 0, &it ); + + boxf region; + + box_init_inf( region ); + box_addpt( region, p0 ); + box_addpt( region, p1 ); + + float k_r = max_dist; + v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] ); + v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] ); + + float closest = k_r*k_r; + struct grind_edge *closest_edge = NULL; + + int idx; + while( bh_next( world.grind_bh, &it, region, &idx ) ) + { + struct grind_edge *edge = &world.grind_edges[ idx ]; + + float s,t; + v3f pa, pb; + + float d2 = + closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb ); + + if( d2 < closest ) + { + closest = d2; + closest_edge = edge; + v3_copy( pa, c0 ); + v3_copy( pb, c1 ); + } + } + + return closest_edge; +} + +/* + * Trace a path given a velocity rotation. + * + * TODO: this MIGHT be worth doing RK4 on the gravity field. + */ +VG_STATIC void player_score_vr_path( v3f co, v3f v, m3x3f vr, + struct land_prediction *prediction ) +{ + float pstep = VG_TIMESTEP_FIXED * 10.0f; + float k_bias = 0.96f; + + v3f pco, pco1, pv; + v3_copy( co, pco ); + v3_muls( v, k_bias, pv ); + + m3x3_mulv( vr, pv, pv ); + v3_muladds( pco, pv, pstep, pco ); + + struct grind_edge *best_grind = NULL; + float closest_grind = INFINITY; + + float grind_score = INFINITY, + air_score = INFINITY; + + prediction->log_length = 0; + + for( int i=0; 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 = 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; 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; + } +} + +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 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; ibias = -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; 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 ) + { + 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 */