#endif
+VG_STATIC int solve_prediction_for_target( player_instance *player,
+ v3f target, float max_angle,
+ struct land_prediction *p )
+{
+ /* calculate the exact solution(s) to jump onto that grind spot */
+
+ v3f v0;
+ v3_sub( target, player->rb.co, v0 );
+
+ v3f ax;
+ v3_copy( v0, ax );
+ ax[1] = 0.0f;
+ v3_normalize( ax );
+
+ v2f d = { v3_dot( v0, ax ), v0[1] },
+ v = { v3_dot( player->rb.v, ax ), player->rb.v[1] };
+
+ float a = atan2f( v[1], v[0] ),
+ m = v2_length( v ),
+ root = m*m*m*m - k_gravity*(k_gravity*d[0]*d[0] + 2.0f*d[1]*m*m);
+
+ if( root > 0.0f )
+ {
+ root = sqrtf( root );
+ float a0 = atanf( (m*m + root) / (k_gravity * d[0]) ),
+ a1 = atanf( (m*m - root) / (k_gravity * d[0]) );
+
+ if( fabsf(a0-a) > fabsf(a1-a) )
+ a0 = a1;
+
+ if( fabsf(a0-a) > max_angle )
+ return 0;
+
+ /* TODO: sweep the path before chosing the smallest dist */
+ /* TODO: Jump in normal direction not to_world[1] */
+ /* TODO: Max Y angle */
+
+ p->log_length = 0;
+ p->land_dist = 0.0f;
+ v3_zero( p->apex );
+ p->type = k_prediction_grind;
+
+ v3_muls( ax, cosf( a0 ) * m, p->v );
+ p->v[1] += sinf( a0 ) * m;
+ p->land_dist = d[0] / (cosf(a0)*m);
+
+ /* add a trace */
+ for( int i=0; i<=20; i++ )
+ {
+ float t = (float)i * (1.0f/20.0f) * p->land_dist;
+
+ v3f p0;
+ v3_muls( p->v, t, p0 );
+ p0[1] += -0.5f * k_gravity * t*t;
+
+ v3_add( player->rb.co, p0, p->log[ p->log_length ++ ] );
+ }
+
+ return 1;
+ }
+ else
+ return 0;
+}
+
VG_STATIC
void player__approximate_best_trajectory( player_instance *player )
{
int idx = spherecast_world( co0, co1, k_board_radius, &t1, n );
if( idx != -1 )
{
+ v3f co;
+ v3_lerp( co0, co1, t1, co );
+ v3_copy( co, p->log[ p->log_length ++ ] );
+
v3_copy( n, p->n );
- v3_lerp( co0, co1, t1, p->log[ p->log_length ++ ] );
p->type = k_prediction_land;
v3f ve;
v3_copy( launch_v, ve );
ve[1] -= k_gravity * t;
- p->score = -v3_dot( ve, n );
+
+ struct grind_info replace_grind;
+ if( skate_grind_scansq( co, ve, 0.3f, &replace_grind ) )
+ {
+ v3_copy( replace_grind.n, p->n );
+ p->type = k_prediction_grind;
+ }
+
+ p->score = -v3_dot( ve, p->n );
p->land_dist = t + k_trace_delta * t1;
+
break;
}
if( grind_located )
{
- v3f v0;
- v3_sub( grind.co, player->rb.co, v0 );
-
- v3f ax;
- v3_copy( v0, ax );
- ax[1] = 0.0f;
- v3_normalize( ax );
-
- v2f d = { v3_dot( v0, ax ), v0[1] },
- v = { v3_dot( player->rb.v, ax ), player->rb.v[1] };
+ /* calculate the exact solution(s) to jump onto that grind spot */
+ struct land_prediction *p = &s->predictions[ s->prediction_count ];
- float a = atan2f( v[1], v[0] ),
- m = v2_length( v ),
-
- root = m*m*m*m - k_gravity*(k_gravity*d[0]*d[0] + 2.0f*d[1]*m*m);
-
- if( root > 0.0f )
+ if( solve_prediction_for_target( player, grind.co, 0.125f*VG_PIf, p ) )
{
- root = sqrtf( root );
- float a0 = atanf( (m*m + root) / (k_gravity * d[0]) ),
- a1 = atanf( (m*m - root) / (k_gravity * d[0]) );
-
- if( fabsf(a0-a) < fabsf(a1-a) )
- a = a0;
- else
- a = a1;
- /* TODO: sweep the path before chosing the smallest dist */
- /* TODO: Jump in normal direction not to_world[1] */
- /* TODO: Grind require manual be pulled in correct direction */
-
- struct land_prediction *p = &s->predictions[ s->prediction_count ++ ];
-
- p->log_length = 0;
- p->land_dist = 0.0f;
- v3_zero( p->apex );
- p->type = k_prediction_grind;
-
- v3_muls( ax, cosf( a ) * m, p->v );
- p->v[1] += sinf( a ) * m;
- p->land_dist = d[0] / (cosf(a)*m);
-
v3_copy( grind.n, p->n );
- /* add a trace */
- for( int i=0; i<=20; i++ )
- {
- float t = (float)i * (1.0f/20.0f) * p->land_dist;
-
- v3f p0;
- v3_muls( p->v, t, p0 );
- p0[1] += -0.5f * k_gravity * t*t;
-
- v3_add( player->rb.co, p0, p->log[ p->log_length ++ ] );
- }
-
/* determine score */
v3f ve;
v3_copy( p->v, ve );
ve[1] -= k_gravity * p->land_dist;
p->score = -v3_dot( ve, grind.n ) * 0.85f;
+
+ s->prediction_count ++;
}
}
-
-
float score_min = INFINITY,
score_max = -INFINITY;
top = 1.5f;
}
- else if( s->state.manual_direction )
- {
- rate = 35.0f;
- top = 1.5f;
- }
-
else if( s->state.activity >= k_skate_activity_grind_any )
{
rate *= fabsf(steer_scaled);
v3_normalize( s->grind_vec );
}
+ else if( s->state.manual_direction )
+ {
+ rate = 35.0f;
+ top = 1.5f;
+ }
+
float current = v3_dot( player->rb.to_world[1], player->rb.w ),
addspeed = (steer_scaled * -top) - current,
maxaccel = rate * k_rb_delta,
}
VG_STATIC void skate_grind_truck_apply( player_instance *player,
- v3f grind_co, struct grind_info *inf,
+ float sign, struct grind_info *inf,
float strength )
{
struct player_skate *s = &player->_skate;
+ /* TODO: Trash compactor this */
+ v3f ra = { 0.0f, -k_board_radius, sign * k_board_length };
+ v3f raw, wsp;
+ m3x3_mulv( player->rb.to_world, ra, raw );
+ v3_add( player->rb.co, raw, wsp );
+
+
+
v3f delta;
- v3_sub( inf->co, grind_co, delta );
+ v3_sub( inf->co, wsp, delta );
/* spring force */
v3_muladds( player->rb.v, delta, k_spring_force*strength*k_rb_delta,
skate_grind_friction( player, inf, strength );
/* yeah yeah yeah yeah */
- v3f raw, axis;
- v3_sub( grind_co, player->rb.co, raw );
- v3_muladds( raw, inf->n, -v3_dot( inf->n, raw ), raw );
- v3_cross( raw, inf->n, axis );
+ v3f raw_nplane, axis;
+ v3_muladds( raw, inf->n, -v3_dot( inf->n, raw ), raw_nplane );
+ v3_cross( raw_nplane, inf->n, axis );
v3_normalize( axis );
/* orientation */
skate_grind_orient( inf, mtx );
v3f target_fwd, fwd, up, target_up;
m3x3_mulv( mtx, s->grind_vec, target_fwd );
- v3_copy( raw, fwd );
+ v3_copy( raw_nplane, fwd );
v3_copy( player->rb.to_world[1], up );
v3_copy( inf->n, target_up );
v3_normalize( fwd );
float way = player->input_js1v->axis.value *
- vg_signf( v3_dot( raw, player->rb.v ) );
+ vg_signf( v3_dot( raw_nplane, player->rb.v ) );
v4f q;
q_axis_angle( q, axis, VG_PIf*0.125f * way );
/* Fake contact */
struct grind_limit *limit = &s->limits[ s->limit_count ++ ];
- m4x3_mulv( player->rb.to_local, grind_co, limit->ra );
+ m4x3_mulv( player->rb.to_local, wsp, limit->ra );
m3x3_mulv( player->rb.to_local, inf->n, limit->n );
limit->p = 0.0f;
+
+ v3_copy( inf->dir, s->grind_dir );
+}
+
+VG_STATIC void skate_5050_apply( player_instance *player,
+ struct grind_info *inf_front,
+ struct grind_info *inf_back )
+{
+ struct player_skate *s = &player->_skate;
+ struct grind_info inf_avg;
}
-VG_STATIC int skate_grind_truck_singular( player_instance *player, float sign )
+VG_STATIC int skate_grind_truck_renew( player_instance *player, float sign,
+ struct grind_info *inf )
{
struct player_skate *s = &player->_skate;
- struct grind_info inf;
v3f wheel_co = { 0.0f, 0.0f, sign * k_board_length },
grind_co = { 0.0f, -k_board_radius, sign * k_board_length };
m4x3_mulv( player->rb.to_world, grind_co, grind_co );
/* Exit condition: lost grind tracking */
- if( !skate_grind_scansq( grind_co, player->rb.v, 0.3f, &inf ) )
+ if( !skate_grind_scansq( grind_co, player->rb.v, 0.3f, inf ) )
return 0;
/* Exit condition: cant see grind target directly */
- if( !skate_point_visible( wheel_co, inf.co ) )
+ if( !skate_point_visible( wheel_co, inf->co ) )
return 0;
/* Exit condition: minimum velocity not reached, but allow a bit of error */
- float dv = fabsf(v3_dot( player->rb.v, inf.dir )),
+ float dv = fabsf(v3_dot( player->rb.v, inf->dir )),
minv = k_grind_axel_min_vel*0.8f;
if( dv < minv )
return 0;
- if( fabsf(v3_dot( inf.dir, s->grind_dir )) < k_grind_max_edge_angle )
+ if( fabsf(v3_dot( inf->dir, s->grind_dir )) < k_grind_max_edge_angle )
return 0;
- v3_copy( inf.dir, s->grind_dir );
-
- float t = vg_clampf( (dv-minv)/(k_grind_axel_min_vel-minv), 0.0f, 1.0f );
- skate_grind_truck_apply( player, grind_co, &inf, t );
+ v3_copy( inf->dir, s->grind_dir );
return 1;
}
-VG_STATIC int skate_truck_entry_condition( player_instance *player, float sign )
+VG_STATIC int skate_grind_truck_entry( player_instance *player, float sign,
+ struct grind_info *inf )
{
struct player_skate *s = &player->_skate;
- struct grind_info inf;
/* TODO: Trash compactor this */
v3f ra = { 0.0f, -k_board_radius, sign * k_board_length };
m3x3_mulv( player->rb.to_world, ra, raw );
v3_add( player->rb.co, raw, wsp );
- if( skate_grind_scansq( wsp, player->rb.v, 0.3, &inf ) )
+ if( skate_grind_scansq( wsp, player->rb.v, 0.3, inf ) )
{
- if( fabsf(v3_dot( player->rb.v, inf.dir )) < k_grind_axel_min_vel )
+ if( fabsf(v3_dot( player->rb.v, inf->dir )) < k_grind_axel_min_vel )
return 0;
/* velocity should be at least 60% aligned */
v3f pv, axis;
- v3_cross( inf.n, inf.dir, axis );
- v3_muladds( player->rb.v, inf.n, -v3_dot( player->rb.v, inf.n ), pv );
+ v3_cross( inf->n, inf->dir, axis );
+ v3_muladds( player->rb.v, inf->n, -v3_dot( player->rb.v, inf->n ), pv );
if( v3_length2( pv ) < 0.0001f )
return 0;
v3_normalize( pv );
- if( fabsf(v3_dot( pv, inf.dir )) < k_grind_axel_max_angle )
+ if( fabsf(v3_dot( pv, inf->dir )) < k_grind_axel_max_angle )
+ return 0;
+
+ if( v3_dot( player->rb.v, inf->n ) > 0.5f )
return 0;
+#if 0
/* check for vertical alignment */
- if( v3_dot( player->rb.to_world[1], inf.n ) < k_grind_axel_max_vangle )
+ if( v3_dot( player->rb.to_world[1], inf->n ) < k_grind_axel_max_vangle )
return 0;
-
- /* TODO: new condition, opposite wheel MUST be in-air or close to it */
+#endif
v3f local_co, local_dir, local_n;
- m4x3_mulv( player->rb.to_local, inf.co, local_co );
- m3x3_mulv( player->rb.to_local, inf.dir, local_dir );
- m3x3_mulv( player->rb.to_local, inf.n, local_n );
+ m4x3_mulv( player->rb.to_local, inf->co, local_co );
+ m3x3_mulv( player->rb.to_local, inf->dir, local_dir );
+ m3x3_mulv( player->rb.to_local, inf->n, local_n );
v2f delta = { local_co[0], local_co[2] - k_board_length*sign };
v3_add( player->rb.v, rv, rv );
if( (local_co[1] >= truck_height) &&
- (v2_length2( delta ) <= k_board_radius*k_board_radius) &&
- (v3_dot( rv, inf.n ) < 0.1f) )
+ (v2_length2( delta ) <= k_board_radius*k_board_radius) )
{
- m3x3f mtx;
- skate_grind_orient( &inf, mtx );
- m3x3_transpose( mtx, mtx );
- m3x3_mulv( mtx, raw, s->grind_vec );
- v3_normalize( s->grind_vec );
- v3_copy( inf.dir, s->grind_dir );
-
- skate_grind_truck_apply( player, wsp, &inf, 1.0f );
return 1;
}
}
vg_line_arrow( player->rb.co, dir, 0.5f, VG__GREEN );
vg_line_arrow( player->rb.co, perp, 0.5f, VG__BLUE );
+
+ v3_copy( inf->dir, s->grind_dir );
}
-VG_STATIC int skate_boardslide_entry_condition( player_instance *player )
+VG_STATIC int skate_boardslide_entry( player_instance *player,
+ struct grind_info *inf )
{
struct player_skate *s = &player->_skate;
- struct grind_info inf;
if( skate_grind_scansq( player->rb.co,
player->rb.to_world[0], k_board_length,
- &inf ) )
+ inf ) )
{
v3f local_co, local_dir;
- m4x3_mulv( player->rb.to_local, inf.co, local_co );
- m3x3_mulv( player->rb.to_local, inf.dir, local_dir );
+ m4x3_mulv( player->rb.to_local, inf->co, local_co );
+ m3x3_mulv( player->rb.to_local, inf->dir, local_dir );
if( (fabsf(local_co[2]) <= k_board_length) && /* within wood area */
(local_co[1] >= 0.0f) && /* at deck level */
(fabsf(local_dir[0]) >= 0.5f) ) /* perpendicular to us */
{
- if( fabsf(v3_dot( player->rb.v, inf.dir )) < k_grind_axel_min_vel )
+ if( fabsf(v3_dot( player->rb.v, inf->dir )) < k_grind_axel_min_vel )
return 0;
- v3_copy( inf.dir, s->grind_dir );
-
- skate_boardslide_apply( player, &inf );
return 1;
}
}
return 0;
}
-VG_STATIC int skate_boardslide_singular( player_instance *player )
+VG_STATIC int skate_boardslide_renew( player_instance *player,
+ struct grind_info *inf )
{
struct player_skate *s = &player->_skate;
- struct grind_info inf;
if( !skate_grind_scansq( player->rb.co,
player->rb.to_world[0], k_board_length,
- &inf ) )
+ inf ) )
return 0;
/* Exit condition: cant see grind target directly */
v3f vis;
v3_muladds( player->rb.co, player->rb.to_world[1], 0.2f, vis );
- if( !skate_point_visible( vis, inf.co ) )
+ if( !skate_point_visible( vis, inf->co ) )
return 0;
/* Exit condition: minimum velocity not reached, but allow a bit of error
* TODO: trash compactor */
- float dv = fabsf(v3_dot( player->rb.v, inf.dir )),
+ float dv = fabsf(v3_dot( player->rb.v, inf->dir )),
minv = k_grind_axel_min_vel*0.8f;
if( dv < minv )
return 0;
- if( fabsf(v3_dot( inf.dir, s->grind_dir )) < k_grind_max_edge_angle )
+ if( fabsf(v3_dot( inf->dir, s->grind_dir )) < k_grind_max_edge_angle )
return 0;
- v3_copy( inf.dir, s->grind_dir );
- float t = vg_clampf( (dv-minv)/(k_grind_axel_min_vel-minv), 0.0f, 1.0f );
-
- skate_boardslide_apply( player, &inf );
return 1;
}
+VG_STATIC void skate_store_grind_vec( player_instance *player,
+ struct grind_info *inf )
+{
+ struct player_skate *s = &player->_skate;
+
+ m3x3f mtx;
+ skate_grind_orient( inf, mtx );
+ m3x3_transpose( mtx, mtx );
+
+ v3f raw;
+ v3_sub( inf->co, player->rb.co, raw );
+
+ m3x3_mulv( mtx, raw, s->grind_vec );
+ v3_normalize( s->grind_vec );
+ v3_copy( inf->dir, s->grind_dir );
+}
+
VG_STATIC enum skate_activity skate_availible_grind( player_instance *player )
{
struct player_skate *s = &player->_skate;
+ /* debounces this state manager a little bit */
+ if( s->frames_since_activity_change < 10 )
+ {
+ s->frames_since_activity_change ++;
+ return k_skate_activity_undefined;
+ }
+
+ struct grind_info inf_back50,
+ inf_front50,
+ inf_slide;
+
+ int res_back50 = 0,
+ res_front50 = 0,
+ res_slide = 0;
+
if( s->state.activity == k_skate_activity_grind_boardslide )
{
- int result = skate_boardslide_singular( player );
+ res_slide = skate_boardslide_renew( player, &inf_slide );
+ }
+ else if( s->state.activity == k_skate_activity_grind_back50 )
+ {
+ res_back50 = skate_grind_truck_renew( player, 1.0f, &inf_back50 );
+ res_front50 = skate_grind_truck_entry( player, -1.0f, &inf_front50 );
+ }
+ else if( s->state.activity == k_skate_activity_grind_front50 )
+ {
+ res_front50 = skate_grind_truck_renew( player, -1.0f, &inf_front50 );
+ res_back50 = skate_grind_truck_entry( player, 1.0f, &inf_back50 );
+ }
+ else if( s->state.activity == k_skate_activity_grind_5050 )
+ {
+ res_front50 = skate_grind_truck_renew( player, -1.0f, &inf_front50 );
+ res_back50 = skate_grind_truck_entry( player, 1.0f, &inf_back50 );
+ }
+ else
+ {
+ res_slide = skate_boardslide_entry( player, &inf_slide );
+ res_back50 = skate_grind_truck_entry( player, 1.0f, &inf_back50 );
+ res_front50 = skate_grind_truck_entry( player, -1.0f, &inf_front50 );
+
+ if( res_back50 != res_front50 )
+ {
+ int wants_to_do_that = fabsf(player->input_js1v->axis.value) >= 0.25f;
+
+ res_back50 &= wants_to_do_that;
+ res_front50 &= wants_to_do_that;
+ }
+ }
+
+ const enum skate_activity table[] =
+ { /* slide | back | front */
+ k_skate_activity_undefined, /* 0 0 0 */
+ k_skate_activity_grind_front50, /* 0 0 1 */
+ k_skate_activity_grind_back50, /* 0 1 0 */
+ k_skate_activity_grind_5050, /* 0 1 1 */
+
+ /* slide has priority always */
+ k_skate_activity_grind_boardslide, /* 1 0 0 */
+ k_skate_activity_grind_boardslide, /* 1 0 1 */
+ k_skate_activity_grind_boardslide, /* 1 1 0 */
+ k_skate_activity_grind_boardslide, /* 1 1 1 */
+ }
+ , new_activity = table[ res_slide << 2 | res_back50 << 1 | res_front50 ];
+
+ if( new_activity == k_skate_activity_undefined )
+ {
+ s->frames_since_activity_change = 0;
+ }
+ else if( new_activity == k_skate_activity_grind_boardslide )
+ {
+ skate_boardslide_apply( player, &inf_slide );
+ }
+ else if( new_activity == k_skate_activity_grind_back50 )
+ {
+ if( s->state.activity != k_skate_activity_grind_back50 )
+ skate_store_grind_vec( player, &inf_back50 );
+
+ skate_grind_truck_apply( player, 1.0f, &inf_back50, 1.0f );
+ }
+ else if( new_activity == k_skate_activity_grind_front50 )
+ {
+ if( s->state.activity != k_skate_activity_grind_front50 )
+ skate_store_grind_vec( player, &inf_front50 );
+
+ skate_grind_truck_apply( player, -1.0f, &inf_front50, 1.0f );
+ }
+ else if( new_activity == k_skate_activity_grind_5050 )
+ skate_5050_apply( player, &inf_front50, &inf_back50 );
+
+ return new_activity;
+
+#if 0
+ if( s->state.activity == k_skate_activity_grind_boardslide )
+ {
+ int res_slide = skate_boardslide_singular( player );
const enum skate_activity table[] =
{
}
return 0;
+#endif
}
VG_STATIC void player__skate_update( player_instance *player )
v3f pos;
float radius;
- int apply_angular;
u32 colour;
enum board_collider_state
{
{ 0.0f, 0.0f, -k_board_length },
.radius = k_board_radius,
- .apply_angular = 1,
.colour = VG__RED
},
{
{ 0.0f, 0.0f, k_board_length },
.radius = k_board_radius,
- .apply_angular = 1,
.colour = VG__GREEN
- },
- {
- { 0.0f, 0.2f, -k_board_length - k_board_end_radius },
- .radius = k_board_end_radius,
- .apply_angular = 0,
- .colour = VG__YELOW
- },
- {
- { 0.0f, 0.2f, k_board_length + k_board_end_radius },
- .radius = k_board_end_radius,
- .apply_angular = 0,
- .colour = VG__YELOW
- },
+ }
};
const int k_wheel_count = 2;
q_mul( rotation, player->rb.q, future_q );
q_normalize( future_q );
}
+ else
+ v4_copy( player->rb.q, future_q );
+
+ v3f future_cg, current_cg, cg_offset;
+ q_mulv( player->rb.q, s->weight_distribution, current_cg );
+ q_mulv( future_q, s->weight_distribution, future_cg );
+ v3_sub( future_cg, current_cg, cg_offset );
/* calculate the minimum time we can move */
float max_time = s->substep;
if( wheels[i].state == k_collider_state_disabled )
continue;
- v3f current, future;
+ v3f current, future, r_cg;
+
q_mulv( future_q, wheels[i].pos, future );
v3_add( future, future_co, future );
+ v3_add( cg_offset, future, future );
q_mulv( player->rb.q, wheels[i].pos, current );
v3_add( current, player->rb.co, current );
v3_divs( axis, mag, axis );
q_axis_angle( rotation, axis, mag*s->substep_delta );
q_mul( rotation, player->rb.q, player->rb.q );
+ q_normalize( player->rb.q );
+
+ q_mulv( player->rb.q, s->weight_distribution, future_cg );
+ v3_sub( current_cg, future_cg, cg_offset );
+ v3_add( player->rb.co, cg_offset, player->rb.co );
}
rb_update_transform( &player->rb );
/*
* Phase 1: Regular collision detection
- * TODO: Me might want to automatically add contacts from CCD,
- * since at high angular velocities, theres a small change
- * that discreet detection will miss.
* --------------------------------------------------------------------------
*/
if( l )
wheels[i].state = k_collider_state_colliding;
- /* for non-angular contacts we just want Y. contact positions are
- * snapped to the local xz plane */
- if( !wheels[i].apply_angular )
- {
- for( int j=0; j<l; j++ )
- {
- v3f ra;
- v3_sub( man[j].co, player->rb.co, ra );
-
- float dy = v3_dot( player->rb.to_world[1], ra );
- v3_muladds( man[j].co, player->rb.to_world[1], -dy, man[j].co );
- }
- }
-
manifold_len += l;
}
* --------------------------------------------------------------------------
*/
+
+ v3f world_cog;
+ m4x3_mulv( player->rb.to_world, s->weight_distribution, world_cog );
+ vg_line_pt3( world_cog, 0.02f, VG__BLACK );
+
for( int i=0; i<manifold_len; i ++ )
{
rb_prepare_contact( &manifold[i], s->substep_delta );
m3x3_mul( iI, player->rb.to_local, iIw );
m3x3_mul( player->rb.to_world, iIw, iIw );
- v3f world_cog;
- m4x3_mulv( player->rb.to_world, s->weight_distribution, world_cog );
- vg_line_pt3( world_cog, 0.02f, VG__BLACK );
-
for( int j=0; j<10; j++ )
{
for( int i=0; i<manifold_len; i++ )