?
[carveJwlIkooP6JGAAIwe30JlM.git] / player_physics.h
1 /*
2 * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
3 */
4
5 #ifndef PLAYER_PHYSICS_H
6 #define PLAYER_PHYSICS_H
7
8 #include "player.h"
9 #include "camera.h"
10
11 VG_STATIC void apply_gravity( v3f vel, float const timestep )
12 {
13 v3f gravity = { 0.0f, -9.6f, 0.0f };
14 v3_muladds( vel, gravity, timestep, vel );
15 }
16
17 VG_STATIC struct
18 grind_edge *player_grind_collect_edge( v3f p0, v3f p1,
19 v3f c0, v3f c1, float max_dist )
20 {
21 struct player_phys *phys = &player.phys;
22
23 bh_iter it;
24 bh_iter_init( 0, &it );
25
26 boxf region;
27
28 box_init_inf( region );
29 box_addpt( region, p0 );
30 box_addpt( region, p1 );
31
32 float k_r = max_dist;
33 v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] );
34 v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] );
35
36 float closest = k_r*k_r;
37 struct grind_edge *closest_edge = NULL;
38
39 int idx;
40 while( bh_next( world.grind_bh, &it, region, &idx ) )
41 {
42 struct grind_edge *edge = &world.grind_edges[ idx ];
43
44 float s,t;
45 v3f pa, pb;
46
47 float d2 =
48 closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb );
49
50 if( d2 < closest )
51 {
52 closest = d2;
53 closest_edge = edge;
54 v3_copy( pa, c0 );
55 v3_copy( pb, c1 );
56 }
57 }
58
59 return closest_edge;
60 }
61
62 /*
63 * Cast a sphere from a to b and see what time it hits
64 */
65 VG_STATIC int spherecast_world( v3f pa, v3f pb, float r, float *t, v3f n )
66 {
67 struct player_phys *phys = &player.phys;
68
69 bh_iter it;
70 bh_iter_init( 0, &it );
71
72 boxf region;
73 box_init_inf( region );
74 box_addpt( region, pa );
75 box_addpt( region, pb );
76
77 v3_add( (v3f){ r, r, r}, region[1], region[1] );
78 v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
79
80 v3f dir;
81 v3_sub( pb, pa, dir );
82
83 v3f dir_inv;
84 dir_inv[0] = 1.0f/dir[0];
85 dir_inv[1] = 1.0f/dir[1];
86 dir_inv[2] = 1.0f/dir[2];
87
88 int hit = -1;
89 float min_t = 1.0f;
90
91 int idx;
92 while( bh_next( world.geo_bh, &it, region, &idx ) )
93 {
94 u32 *ptri = &world.scene_geo->arrindices[ idx*3 ];
95 v3f tri[3];
96
97 boxf box;
98 box_init_inf( box );
99
100 for( int j=0; j<3; j++ )
101 {
102 v3_copy( world.scene_geo->arrvertices[ptri[j]].co, tri[j] );
103 box_addpt( box, tri[j] );
104 }
105
106 v3_add( (v3f){ r, r, r}, box[1], box[1] );
107 v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
108
109 if( !ray_aabb1( box, pa, dir_inv, 1.0f ) )
110 continue;
111
112 float t;
113 v3f n1;
114 if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) )
115 {
116 if( t < min_t )
117 {
118 min_t = t;
119 hit = idx;
120 v3_copy( n1, n );
121 }
122 }
123 }
124
125 *t = min_t;
126 return hit;
127 }
128
129 /*
130 * Trace a path given a velocity rotation.
131 * Closest to 0 is best.
132 */
133 VG_STATIC void player_predict_land( m3x3f vr,
134 struct land_prediction *prediction )
135 {
136 struct player_phys *phys = &player.phys;
137
138 float pstep = VG_TIMESTEP_FIXED * 10.0f;
139 float k_bias = 0.96f;
140
141 v3f pco, pco1, pv;
142 v3_copy( phys->rb.co, pco );
143 v3_muls( phys->rb.v, k_bias, pv );
144
145 m3x3_mulv( vr, pv, pv );
146 v3_muladds( pco, pv, pstep, pco );
147
148 struct grind_edge *best_grind = NULL;
149 float closest_grind = INFINITY;
150
151 float grind_score = INFINITY,
152 air_score = INFINITY;
153
154 prediction->log_length = 0;
155
156 for( int i=0; i<vg_list_size(prediction->log); i++ )
157 {
158 v3_copy( pco, pco1 );
159 apply_gravity( pv, pstep );
160
161 m3x3_mulv( vr, pv, pv );
162 v3_muladds( pco, pv, pstep, pco );
163
164 v3f vdir;
165
166 v3_sub( pco, pco1, vdir );
167
168 float l = v3_length( vdir );
169 v3_muls( vdir, 1.0f/l, vdir );
170
171 v3f c0, c1;
172 struct grind_edge *ge = player_grind_collect_edge( pco, pco1,
173 c0, c1, 0.4f );
174
175 if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
176 {
177 float d2 = v3_dist2( c0, c1 );
178 if( d2 < closest_grind )
179 {
180 closest_grind = d2;
181 best_grind = ge;
182 grind_score = closest_grind * 0.05f;
183 }
184 }
185
186 v3f n1;
187
188 float t1;
189 int idx = spherecast_world( pco1, pco, 0.4f, &t1, n1 );
190 if( idx != -1 )
191 {
192 v3_copy( n1, prediction->n );
193 air_score = -v3_dot( pv, n1 );
194
195 u32 vert_index = world.scene_geo->arrindices[ idx*3 ];
196 struct world_material *mat = world_tri_index_material( vert_index );
197
198 /* Bias prediction towords ramps */
199 if( mat->info.flags & k_material_flag_skate_surface )
200 air_score *= 0.1f;
201
202 v3_lerp( pco1, pco, t1, prediction->log[ prediction->log_length ++ ] );
203 break;
204 }
205
206 v3_copy( pco, prediction->log[ prediction->log_length ++ ] );
207 }
208
209 if( grind_score < air_score )
210 {
211 prediction->score = grind_score;
212 prediction->type = k_prediction_grind;
213 }
214 else if( air_score < INFINITY )
215 {
216 prediction->score = air_score;
217 prediction->type = k_prediction_land;
218 }
219 else
220 {
221 prediction->score = INFINITY;
222 prediction->type = k_prediction_none;
223 }
224 }
225
226 /*
227 * Called when launching into the air to predict and adjust trajectories
228 */
229 VG_STATIC void player_start_air(void)
230 {
231 struct player_phys *phys = &player.phys;
232
233 float pstep = VG_TIMESTEP_FIXED * 10.0f;
234 float best_velocity_delta = -9999.9f;
235
236 v3f axis;
237 v3_cross( phys->rb.up, phys->rb.v, axis );
238 v3_normalize( axis );
239 player.prediction_count = 0;
240
241 m3x3_identity( phys->vr );
242
243 float
244 best_vmod = 0.0f,
245 min_score = INFINITY,
246 max_score = -INFINITY;
247
248 /*
249 * Search a broad selection of futures
250 */
251 for( int m=-3;m<=12; m++ )
252 {
253 struct land_prediction *p =
254 &player.predictions[ player.prediction_count ++ ];
255
256 float vmod = ((float)m / 15.0f)*0.09f;
257
258 m3x3f vr;
259 v4f vr_q;
260
261 q_axis_angle( vr_q, axis, vmod );
262 q_m3x3( vr_q, vr );
263
264 player_predict_land( vr, p );
265
266 if( p->type != k_prediction_none )
267 {
268 if( p->score < min_score )
269 {
270 min_score = p->score;
271 best_vmod = vmod;
272 }
273
274 if( p->score > max_score )
275 max_score = p->score;
276 }
277 }
278
279 v4f vr_q;
280 q_axis_angle( vr_q, axis, best_vmod*0.1f );
281 q_m3x3( vr_q, phys->vr );
282
283 q_axis_angle( vr_q, axis, best_vmod );
284 q_m3x3( vr_q, phys->vr_pstep );
285
286 /*
287 * Logging
288 */
289 for( int i=0; i<player.prediction_count; i ++ )
290 {
291 struct land_prediction *p = &player.predictions[i];
292
293 float l = p->score;
294
295 if( l < 0.0f )
296 {
297 vg_error( "negative score! (%f)\n", l );
298 }
299
300 l -= min_score;
301 l /= (max_score-min_score);
302 l = 1.0f - l;
303 l *= 255.0f;
304
305 p->colour = l;
306 p->colour <<= 8;
307 p->colour |= 0xff000000;
308 }
309 }
310
311
312 VG_STATIC void player_physics_control_passive(void)
313 {
314 struct player_phys *phys = &player.phys;
315 float grabt = player.input_grab->axis.value;
316
317 if( grabt > 0.5f )
318 {
319 v2_muladds( phys->grab_mouse_delta, vg.mouse_delta, 0.02f,
320 phys->grab_mouse_delta );
321 v2_normalize_clamp( phys->grab_mouse_delta );
322
323 if( freecam )
324 v2_zero( phys->grab_mouse_delta );
325 }
326 else
327 v2_zero( phys->grab_mouse_delta );
328
329 phys->grab = vg_lerpf( phys->grab, grabt, 0.14f );
330 player.phys.pushing = 0.0f;
331
332 if( !phys->jump_charge || phys->in_air )
333 {
334 phys->jump -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
335 }
336
337 phys->jump_charge = 0;
338 phys->jump = vg_clampf( phys->jump, 0.0f, 1.0f );
339 }
340
341 /*
342 * Main friction interface model
343 */
344 VG_STATIC void player_physics_control(void)
345 {
346 struct player_phys *phys = &player.phys;
347
348 /*
349 * Computing localized friction forces for controlling the character
350 * Friction across X is significantly more than Z
351 */
352
353 v3f vel;
354 m3x3_mulv( phys->rb.to_local, phys->rb.v, vel );
355 float slip = 0.0f;
356
357 if( fabsf(vel[2]) > 0.01f )
358 slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
359
360 if( fabsf( slip ) > 1.2f )
361 slip = vg_signf( slip ) * 1.2f;
362 phys->slip = slip;
363 phys->reverse = -vg_signf(vel[2]);
364
365 float substep = VG_TIMESTEP_FIXED;
366 float fwd_resistance = k_friction_resistance;
367
368 vel[2] = stable_force( vel[2],vg_signf(vel[2]) * -fwd_resistance*substep);
369 vel[0] = stable_force( vel[0],vg_signf(vel[0]) * -k_friction_lat*substep);
370
371 if( player.input_jump->button.value )
372 {
373 phys->jump += VG_TIMESTEP_FIXED * k_jump_charge_speed;
374
375 if( !phys->jump_charge )
376 phys->jump_dir = phys->reverse > 0.0f? 1: 0;
377
378 phys->jump_charge = 1;
379 }
380
381 static int push_thresh_last = 0;
382 float push = player.input_push->button.value;
383 int push_thresh = push>0.15f? 1: 0;
384
385 if( push_thresh && !push_thresh_last )
386 player.phys.start_push = vg.time;
387
388 push_thresh_last = push_thresh;
389
390 if( !player.input_jump->button.value && push_thresh )
391 {
392 player.phys.pushing = 1.0f;
393 player.phys.push_time = vg.time - player.phys.start_push;
394
395 float cycle_time = player.phys.push_time*k_push_cycle_rate,
396 amt = k_push_accel * (sinf(cycle_time)*0.5f+0.5f)*VG_TIMESTEP_FIXED,
397 current = v3_length( vel ),
398 new_vel = vg_minf( current + amt, k_max_push_speed );
399
400 new_vel -= vg_minf(current, k_max_push_speed);
401 vel[2] -= new_vel * phys->reverse;
402 }
403
404 m3x3_mulv( phys->rb.to_world, vel, phys->rb.v );
405
406 float input = player.input_js1h->axis.value,
407 grab = player.input_grab->axis.value,
408 steer = input * (1.0f-(phys->jump+grab)*0.4f),
409 steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
410
411 phys->iY -= steer_scaled * VG_TIMESTEP_FIXED;
412
413
414 /*
415 * EXPERIMENTAL
416 * ===============================================
417 */
418 #if 0
419 v3f cog_ideal, diff;
420
421 v3_muladds( phys->rb.co, phys->rb.up, 1.0f-grab, cog_ideal );
422 v3_sub( cog_ideal, phys->cog, diff );
423
424 /* temp */
425 if( v3_length2( diff ) > 20.0f*20.0f )
426 v3_copy( cog_ideal, phys->cog );
427 else
428 {
429 float rate_lat = k_cog_spring_lat * VG_TIMESTEP_FIXED,
430 rate_vert = k_cog_spring_vert * VG_TIMESTEP_FIXED,
431 vert_amt = v3_dot( diff, phys->rb.up );
432
433 /* Split into vert/lat springs */
434 v3f diff_vert, diff_lat;
435 v3_muladds( diff, phys->rb.up, -vert_amt, diff_lat );
436 v3_muls( phys->rb.up, vert_amt, diff_vert );
437
438
439 if( diff[1] > 0.0f )
440 rate_vert *= k_cog_boost_multiplier;
441
442 float ap_a = k_cog_mass_ratio,
443 ap_b = -(1.0f-k_cog_mass_ratio);
444
445 v3_muladds( phys->cog_v, diff_lat, rate_lat * ap_a, phys->cog_v );
446 v3_muladds( phys->cog_v, diff_vert, rate_vert * ap_a, phys->cog_v );
447
448 //v3_muladds( phys->rb.v, diff_lat, rate_lat * ap_b, phys->rb.v );
449 v3_muladds( phys->rb.v, diff_vert, rate_vert * ap_b, phys->rb.v );
450
451 /* dampen */
452 v3_muls( phys->cog_v, 1.0f-(VG_TIMESTEP_FIXED*k_cog_damp), phys->cog_v );
453
454 /* integrate */
455 v3_muladds( phys->cog, phys->cog_v, VG_TIMESTEP_FIXED, phys->cog );
456 }
457
458
459 /*
460 * EXPERIMENTAL
461 * ===============================================
462 */
463 #endif
464
465
466 if( !phys->jump_charge && phys->jump > 0.2f )
467 {
468 v3f jumpdir;
469
470 /* Launch more up if alignment is up else improve velocity */
471 float aup = fabsf(v3_dot( (v3f){0.0f,1.0f,0.0f}, phys->rb.up )),
472 mod = 0.5f,
473 dir = mod + aup*(1.0f-mod);
474
475 v3_copy( phys->rb.v, jumpdir );
476 v3_normalize( jumpdir );
477 v3_muls( jumpdir, 1.0f-dir, jumpdir );
478 v3_muladds( jumpdir, phys->rb.up, dir, jumpdir );
479 v3_normalize( jumpdir );
480
481 float force = k_jump_force*phys->jump;
482 v3_muladds( phys->rb.v, jumpdir, force, phys->rb.v );
483 phys->jump = 0.0f;
484
485 player.jump_time = vg.time;
486
487 /* TODO: Move to audio file */
488 audio_lock();
489 audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
490 audio_player_set_position( &audio_player_extra, phys->rb.co );
491 audio_player_set_vol( &audio_player_extra, 20.0f );
492 audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
493 audio_unlock();
494 }
495 }
496
497 VG_STATIC void player_physics_control_grind(void)
498 {
499 struct player_phys *phys = &player.phys;
500 v2f steer = { player.input_js1h->axis.value,
501 player.input_js1v->axis.value };
502
503 float l2 = v2_length2( steer );
504 if( l2 > 1.0f )
505 v2_muls( steer, 1.0f/sqrtf(l2), steer );
506
507 phys->iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
508
509 float iX = steer[1] * phys->reverse * k_steer_air * VG_TIMESTEP_FIXED;
510
511 static float siX = 0.0f;
512 siX = vg_lerpf( siX, iX, k_steer_air_lerp );
513
514 v4f rotate;
515 q_axis_angle( rotate, phys->rb.right, siX );
516 q_mul( rotate, phys->rb.q, phys->rb.q );
517
518 phys->slip = 0.0f;
519 }
520
521 /*
522 * Air control, no real physics
523 */
524 VG_STATIC void player_physics_control_air(void)
525 {
526 struct player_phys *phys = &player.phys;
527
528 m3x3_mulv( phys->vr, phys->rb.v, phys->rb.v );
529 //vg_line_cross( player.land_target, 0xff0000ff, 0.25f );
530
531 ray_hit hit;
532
533 /*
534 * Prediction
535 */
536 float pstep = VG_TIMESTEP_FIXED * 1.0f;
537 float k_bias = 0.98f;
538
539 v3f pco, pco1, pv;
540 v3_copy( phys->rb.co, pco );
541 v3_muls( phys->rb.v, 1.0f, pv );
542
543 float time_to_impact = 0.0f;
544 float limiter = 1.0f;
545
546 struct grind_edge *best_grind = NULL;
547 float closest_grind = INFINITY;
548
549 v3f target_normal = { 0.0f, 1.0f, 0.0f };
550 int has_target = 0;
551
552 for( int i=0; i<250; i++ )
553 {
554 v3_copy( pco, pco1 );
555 m3x3_mulv( phys->vr, pv, pv );
556 apply_gravity( pv, pstep );
557 v3_muladds( pco, pv, pstep, pco );
558
559 ray_hit contact;
560 v3f vdir;
561
562 v3_sub( pco, pco1, vdir );
563 contact.dist = v3_length( vdir );
564 v3_divs( vdir, contact.dist, vdir);
565
566 v3f c0, c1;
567 struct grind_edge *ge = player_grind_collect_edge( pco, pco1,
568 c0, c1, 0.4f );
569
570 if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
571 {
572 vg_line( ge->p0, ge->p1, 0xff0000ff );
573 vg_line_cross( pco, 0xff0000ff, 0.25f );
574 has_target = 1;
575 break;
576 }
577
578 float orig_dist = contact.dist;
579 if( ray_world( pco1, vdir, &contact ) )
580 {
581 v3_copy( contact.normal, target_normal );
582 has_target = 1;
583 time_to_impact += (contact.dist/orig_dist)*pstep;
584 vg_line_cross( contact.pos, 0xffff0000, 0.25f );
585 break;
586 }
587 time_to_impact += pstep;
588 }
589
590 if( has_target )
591 {
592 float angle = v3_dot( phys->rb.up, target_normal );
593 v3f axis;
594 v3_cross( phys->rb.up, target_normal, axis );
595
596 limiter = vg_minf( 5.0f, time_to_impact )/5.0f;
597 limiter = 1.0f-limiter;
598 limiter *= limiter;
599 limiter = 1.0f-limiter;
600
601 if( fabsf(angle) < 0.99f )
602 {
603 v4f correction;
604 q_axis_angle( correction, axis,
605 acosf(angle)*(1.0f-limiter)*3.0f*VG_TIMESTEP_FIXED );
606 q_mul( correction, phys->rb.q, phys->rb.q );
607 }
608 }
609
610 v2f steer = { player.input_js1h->axis.value,
611 player.input_js1v->axis.value };
612
613 float l2 = v2_length2( steer );
614 if( l2 > 1.0f )
615 v2_muls( steer, 1.0f/sqrtf(l2), steer );
616
617 phys->iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
618
619 float iX = steer[1] *
620 phys->reverse * k_steer_air * limiter * VG_TIMESTEP_FIXED;
621
622 static float siX = 0.0f;
623 siX = vg_lerpf( siX, iX, k_steer_air_lerp );
624
625 v4f rotate;
626 q_axis_angle( rotate, phys->rb.right, siX );
627 q_mul( rotate, phys->rb.q, phys->rb.q );
628
629 #if 0
630 v2f target = {0.0f,0.0f};
631 v2_muladds( target, (v2f){ vg_get_axis("grabh"), vg_get_axis("grabv") },
632 phys->grab, target );
633 #endif
634 }
635
636 VG_STATIC void player_walk_collider_configuration(void)
637 {
638 struct player_phys *phys = &player.phys;
639 float h0 = 0.3f,
640 h1 = 0.9f;
641
642 rigidbody *rbf = &player.collide_front,
643 *rbb = &player.collide_back;
644
645 v3_add( phys->rb.co, (v3f){0.0f,h0,0.0f}, rbf->co );
646 v3_add( phys->rb.co, (v3f){0.0f,h1,0.0f}, rbb->co );
647 v3_copy( rbf->co, rbf->to_world[3] );
648 v3_copy( rbb->co, rbb->to_world[3] );
649 m4x3_invert_affine( rbf->to_world, rbf->to_local );
650 m4x3_invert_affine( rbb->to_world, rbb->to_local );
651
652 rb_update_bounds( rbf );
653 rb_update_bounds( rbb );
654 }
655
656 VG_STATIC void player_regular_collider_configuration(void)
657 {
658 struct player_phys *phys = &player.phys;
659
660 /* Standard ground configuration */
661 rigidbody *rbf = &player.collide_front,
662 *rbb = &player.collide_back;
663
664 m3x3_copy( phys->rb.to_world, player.collide_front.to_world );
665 m3x3_copy( phys->rb.to_world, player.collide_back.to_world );
666
667 player.air_blend = vg_lerpf( player.air_blend, phys->in_air, 0.1f );
668 float h = player.air_blend*0.0f;
669
670 m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h,-k_board_length}, rbf->co );
671 v3_copy( rbf->co, rbf->to_world[3] );
672 m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h, k_board_length}, rbb->co );
673 v3_copy( rbb->co, rbb->to_world[3] );
674
675 m4x3_invert_affine( rbf->to_world, rbf->to_local );
676 m4x3_invert_affine( rbb->to_world, rbb->to_local );
677
678 rb_update_bounds( rbf );
679 rb_update_bounds( rbb );
680 }
681
682 VG_STATIC void player_integrate(void);
683
684 VG_STATIC int player_walk_surface_standable( v3f n )
685 {
686 return v3_dot( n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f;
687 }
688
689 VG_STATIC void player_walk_stepdown(void)
690 {
691 struct player_phys *phys = &player.phys;
692 float max_dist = 0.4f;
693
694 v3f pa, pb;
695 v3_copy( phys->rb.co, pa );
696 pa[1] += 0.3f;
697
698 v3_muladds( pa, (v3f){0.01f,1.0f,0.01f}, -max_dist, pb );
699 vg_line( pa, pb, 0xff000000 );
700
701 /* TODO: Make #define */
702 float r = 0.3f,
703 t;
704
705 v3f n;
706 if( spherecast_world( pa, pb, r, &t, n ) != -1 )
707 {
708 if( player_walk_surface_standable( n ) )
709 {
710 phys->in_air = 0;
711 v3_lerp( pa, pb, t+0.001f, phys->rb.co );
712 phys->rb.co[1] -= 0.3f;
713 }
714 }
715 }
716
717 VG_STATIC int player_update_collision_manifold( rb_ct *manifold );
718 VG_STATIC void player_walk_physics(void)
719 {
720 struct player_phys *phys = &player.phys;
721 rigidbody *rbf = &player.collide_front,
722 *rbb = &player.collide_back;
723
724 m3x3_identity( player.collide_front.to_world );
725 m3x3_identity( player.collide_back.to_world );
726
727 v3_zero( phys->rb.w );
728 q_axis_angle( phys->rb.q, (v3f){0.0f,1.0f,0.0f}, -player.angles[0] );
729
730 rb_ct manifold[64];
731 int len;
732
733 v3f forward_dir = { sinf(player.angles[0]),0.0f,-cosf(player.angles[0]) };
734 v3f right_dir = { -forward_dir[2], 0.0f, forward_dir[0] };
735
736 v2f walk = { player.input_walkh->axis.value,
737 player.input_walkv->axis.value };
738
739 if( freecam )
740 v2_zero( walk );
741
742 if( v2_length2(walk) > 0.001f )
743 v2_normalize_clamp( walk );
744
745 if( phys->in_air )
746 {
747 player_walk_collider_configuration();
748
749 /* allow player to accelerate a bit */
750 v3f walk_3d;
751 v3_muls( forward_dir, walk[1], walk_3d );
752 v3_muladds( walk_3d, right_dir, walk[0], walk_3d );
753
754 float current_vel = fabsf(v3_dot( walk_3d, phys->rb.v )),
755 new_vel = current_vel + VG_TIMESTEP_FIXED*k_air_accelerate,
756 clamped_new = vg_clampf( new_vel, 0.0f, k_walkspeed ),
757 vel_diff = vg_maxf( 0.0f, clamped_new - current_vel );
758
759 v3_muladds( phys->rb.v, right_dir, walk[0] * vel_diff, phys->rb.v );
760 v3_muladds( phys->rb.v, forward_dir, walk[1] * vel_diff, phys->rb.v );
761
762
763 len = player_update_collision_manifold( manifold );
764 rb_presolve_contacts( manifold, len );
765
766 for( int i=0; i<len; i++ )
767 {
768 struct contact *ct = &manifold[i];
769 if( v3_dot( ct->n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f )
770 phys->in_air = 0;
771 }
772
773 for( int j=0; j<5; j++ )
774 {
775 for( int i=0; i<len; i++ )
776 {
777 struct contact *ct = &manifold[i];
778
779 /*normal */
780 float vn = -v3_dot( phys->rb.v, ct->n );
781 vn += ct->bias;
782
783 float temp = ct->norm_impulse;
784 ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
785 vn = ct->norm_impulse - temp;
786
787 v3f impulse;
788 v3_muls( ct->n, vn, impulse );
789
790 v3_add( impulse, phys->rb.v, phys->rb.v );
791
792 /* friction */
793 for( int j=0; j<2; j++ )
794 {
795 float f = k_friction * ct->norm_impulse,
796 vt = v3_dot( phys->rb.v, ct->t[j] ),
797 lambda = -vt;
798
799 float temp = ct->tangent_impulse[j];
800 ct->tangent_impulse[j] = vg_clampf( temp + lambda, -f, f );
801 lambda = ct->tangent_impulse[j] - temp;
802
803 v3_muladds( phys->rb.v, ct->t[j], lambda, phys->rb.v );
804 }
805 }
806 }
807
808 player_integrate();
809 }
810 else
811 {
812 player.walk = v2_length( walk );
813
814 if( player.input_walk->button.value )
815 v2_muls( walk, 0.5f, walk );
816
817 v2_muls( walk, k_walkspeed * VG_TIMESTEP_FIXED, walk );
818
819 /* Do XY translation */
820 v3f walk_apply, walk_measured;
821 v3_zero( walk_apply );
822 v3_muladds( walk_apply, right_dir, walk[0], walk_apply );
823 v3_muladds( walk_apply, forward_dir, walk[1], walk_apply );
824 v3_add( walk_apply, phys->rb.co, phys->rb.co );
825
826 /* Directly resolve collisions */
827 player_walk_collider_configuration();
828 len = player_update_collision_manifold( manifold );
829
830 v3f dt;
831 v3_zero( dt );
832 for( int j=0; j<8; j++ )
833 {
834 for( int i=0; i<len; i++ )
835 {
836 struct contact *ct = &manifold[i];
837
838 float resolved_amt = v3_dot( ct->n, dt ),
839 remaining = (ct->p-k_penetration_slop) - resolved_amt,
840 apply = vg_maxf( remaining, 0.0f ) * 0.3f;
841
842 v3_muladds( dt, ct->n, apply, dt );
843 }
844 }
845 v3_add( dt, phys->rb.co, phys->rb.co );
846
847 v3_add( dt, walk_apply, walk_measured );
848 v3_divs( walk_measured, VG_TIMESTEP_FIXED, phys->rb.v );
849
850 if( len )
851 {
852 struct world_material *surface_mat = world_contact_material(manifold);
853 player.surface_prop = surface_mat->info.surface_prop;
854 }
855
856 /* jump */
857 if( player.input_jump->button.value )
858 {
859 phys->rb.v[1] = 5.0f;
860 phys->in_air = 1;
861 return;
862 }
863
864 /* Check if grounded by current manifold */
865 phys->in_air = 1;
866 for( int i=0; i<len; i++ )
867 {
868 struct contact *ct = &manifold[i];
869 if( player_walk_surface_standable( ct->n ) )
870 phys->in_air = 0;
871 }
872
873 /* otherwise... */
874 if( phys->in_air )
875 player_walk_stepdown();
876 }
877 }
878
879 VG_STATIC void player_grind(void)
880 {
881 struct player_phys *phys = &player.phys;
882
883 v3f closest;
884 int idx = bh_closest_point( world.grind_bh, phys->rb.co, closest, INFINITY );
885 if( idx == -1 )
886 return;
887
888 struct grind_edge *edge = &world.grind_edges[ idx ];
889
890 vg_line( phys->rb.co, closest, 0xff000000 );
891 vg_line_cross( closest, 0xff000000, 0.3f );
892 vg_line( edge->p0, edge->p1, 0xff000000 );
893
894 v3f grind_delta;
895 v3_sub( closest, phys->rb.co, grind_delta );
896
897 float p = v3_dot( phys->rb.forward, grind_delta );
898 v3_muladds( grind_delta, phys->rb.forward, -p, grind_delta );
899
900 float a = vg_maxf( 0.0f, 4.0f-v3_dist2( closest, phys->rb.co ) );
901 v3_muladds( phys->rb.v, grind_delta, a*0.2f, phys->rb.v );
902 }
903
904 VG_STATIC int player_update_grind_collision( rb_ct *contact )
905 {
906 struct player_phys *phys = &player.phys;
907
908 v3f p0, p1, c0, c1;
909 v3_muladds( phys->rb.co, phys->rb.forward, 0.5f, p0 );
910 v3_muladds( phys->rb.co, phys->rb.forward, -0.5f, p1 );
911 v3_muladds( p0, phys->rb.up, 0.125f-0.15f, p0 );
912 v3_muladds( p1, phys->rb.up, 0.125f-0.15f, p1 );
913
914 float const k_r = 0.25f;
915 struct grind_edge *closest_edge = player_grind_collect_edge( p0, p1,
916 c0, c1, k_r );
917
918
919 #if 0
920 vg_line( p0, p1, 0xff0000ff );
921 #endif
922
923 if( closest_edge )
924 {
925 #if 0
926 vg_line_cross( c0, 0xff000000, 0.1f );
927 vg_line_cross( c1, 0xff000000, 0.1f );
928 vg_line( c0, c1, 0xff000000 );
929 #endif
930
931 v3f delta;
932 v3_sub( c1, c0, delta );
933
934 if( v3_dot( delta, phys->rb.up ) > 0.0001f )
935 {
936 contact->p = v3_length( delta );
937 contact->type = k_contact_type_edge;
938 contact->element_id = 0;
939 v3_copy( c1, contact->co );
940 contact->rba = &player.phys.rb;
941 contact->rbb = &world.rb_geo;
942
943 v3f edge_dir, axis_dir;
944 v3_sub( closest_edge->p1, closest_edge->p0, edge_dir );
945 v3_normalize( edge_dir );
946 v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
947 v3_cross( edge_dir, axis_dir, contact->n );
948
949 #if 0
950 vg_info( "%f %f\n", v3_length( contact->n ), contact->p );
951 #endif
952
953 return 1;
954 }
955 else
956 return -1;
957 }
958
959 return 0;
960 }
961
962 /* Manifold must be able to hold at least 64 elements */
963 VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
964 {
965 struct player_phys *phys = &player.phys;
966
967 rigidbody *rbf = &player.collide_front,
968 *rbb = &player.collide_back;
969
970 rb_debug( rbf, 0xff00ffff );
971 rb_debug( rbb, 0xffffff00 );
972
973
974 #if 0
975 phys->rise = vg_lerpf( phys->rise, phys->in_air? -0.25f: 0.0f,
976 VG_TIMESTEP_FIXED );
977 #endif
978
979 int len_f = 0,
980 len_b = 0;
981
982 len_f = rb_sphere_scene( rbf, &world.rb_geo, manifold );
983 rb_manifold_filter_coplanar( manifold, len_f, 0.05f );
984 if( len_f > 1 )
985 {
986 rb_manifold_filter_backface( manifold, len_f );
987 rb_manifold_filter_joint_edges( manifold, len_f, 0.05f );
988 rb_manifold_filter_pairs( manifold, len_f, 0.05f );
989 }
990 int new_len_f = rb_manifold_apply_filtered( manifold, len_f );
991 if( len_f && !new_len_f )
992 len_f = 1;
993 else
994 len_f = new_len_f;
995
996 rb_ct *man_b = &manifold[len_f];
997 len_b = rb_sphere_scene( rbb, &world.rb_geo, man_b );
998 rb_manifold_filter_coplanar( man_b, len_b, 0.05f );
999 if( len_b > 1 )
1000 {
1001 rb_manifold_filter_backface( man_b, len_b );
1002 rb_manifold_filter_joint_edges( man_b, len_b, 0.05f );
1003 rb_manifold_filter_pairs( man_b, len_b, 0.05f );
1004 }
1005 int new_len_b = rb_manifold_apply_filtered( man_b, len_b );
1006 if( len_b && !new_len_b )
1007 len_b = 1;
1008 else
1009 len_b = new_len_b;
1010
1011 return len_f + len_b;
1012 }
1013
1014 VG_STATIC void player_adhere_ground( rb_ct *manifold, int len )
1015 {
1016 struct player_phys *phys = &player.phys;
1017 int was_in_air = phys->in_air;
1018
1019 v3f surface_avg;
1020 v3_zero( surface_avg );
1021
1022 /*
1023 *
1024 * EXPERIMENTAL
1025 * ================================================================
1026 */
1027 if( phys->in_air )
1028 player.normal_pressure = 0.0f;
1029 else
1030 player.normal_pressure = v3_dot( phys->rb.up, phys->rb.v );
1031
1032 v3f p0_0, p0_1,
1033 p1_0, p1_1,
1034 n0, n1;
1035 float t0, t1;
1036
1037 float mod = 0.7f * player.input_grab->axis.value + 0.3f,
1038 spring_k = mod * k_spring_force,
1039 damp_K = mod * k_spring_dampener,
1040 disp_k = 0.4f;
1041
1042 v3_copy( player.collide_front.co, p0_0 );
1043 v3_copy( player.collide_back.co, p1_0 );
1044
1045 v3_muladds( p0_0, phys->rb.up, -disp_k, p0_1 );
1046 v3_muladds( p1_0, phys->rb.up, -disp_k, p1_1 );
1047
1048 int cast0 = spherecast_world( p0_0, p0_1, 0.2f, &t0, n0 ),
1049 cast1 = spherecast_world( p1_0, p1_1, 0.2f, &t1, n1 );
1050
1051 v3f animp0, animp1;
1052
1053 m4x3f temp;
1054 m3x3_copy( phys->rb.to_world, temp );
1055 if( cast0 != -1 )
1056 {
1057 v3_lerp( p0_0, p0_1, t0, temp[3] );
1058 v3_copy( temp[3], animp0 );
1059 debug_sphere( temp, 0.2f, VG__PINK );
1060
1061 v3f F, delta;
1062 v3_sub( p0_0, phys->rb.co, delta );
1063
1064 float displacement = vg_clampf( 1.0f-t0, 0.0f, 1.0f ),
1065 damp = vg_maxf( 0.0f, v3_dot( phys->rb.up, phys->rb.v ) );
1066 v3_muls( phys->rb.up, displacement*spring_k*k_rb_delta -
1067 damp*damp_K*k_rb_delta, F );
1068
1069 v3_muladds( phys->rb.v, F, 1.0f, phys->rb.v );
1070
1071 /* Angular velocity */
1072 v3f wa;
1073 v3_cross( delta, F, wa );
1074 v3_muladds( phys->rb.w, wa, k_spring_angular, phys->rb.w );
1075 }
1076 else
1077 v3_copy( p0_1, animp0 );
1078
1079 if( cast1 != -1 )
1080 {
1081 v3_lerp( p1_0, p1_1, t1, temp[3] );
1082 v3_copy( temp[3], animp1 );
1083 debug_sphere( temp, 0.2f, VG__PINK );
1084
1085 v3f F, delta;
1086 v3_sub( p1_0, phys->rb.co, delta );
1087
1088 float displacement = vg_clampf( 1.0f-t1, 0.0f, 1.0f ),
1089 damp = vg_maxf( 0.0f, v3_dot( phys->rb.up, phys->rb.v ) );
1090 v3_muls( phys->rb.up, displacement*spring_k*k_rb_delta -
1091 damp*damp_K*k_rb_delta, F );
1092
1093 v3_muladds( phys->rb.v, F, 1.0f, phys->rb.v );
1094
1095 /* Angular velocity */
1096 v3f wa;
1097 v3_cross( delta, F, wa );
1098 v3_muladds( phys->rb.w, wa, k_spring_angular, phys->rb.w );
1099 }
1100 else
1101 v3_copy( p1_1, animp1 );
1102
1103 v3f animavg, animdelta;
1104 v3_add( animp0, animp1, animavg );
1105 v3_muls( animavg, 0.5f, animavg );
1106
1107 v3_sub( animp1, animp0, animdelta );
1108 v3_normalize( animdelta );
1109
1110 m4x3_mulv( phys->rb.to_local, animavg, player.board_offset );
1111
1112 float dx = -v3_dot( animdelta, phys->rb.forward ),
1113 dy = v3_dot( animdelta, phys->rb.up );
1114
1115 float angle = -atan2f( dy, dx );
1116 q_axis_angle( player.board_rotation, (v3f){ 1.0f, 0.0f, 0.0f }, angle );
1117
1118 /*
1119 * ================================================================
1120 * EXPERIMENTAL
1121 */
1122
1123 if( len == 0 && !((cast0 !=-1)&&(cast1!=-1)) )
1124 {
1125 phys->lift_frames ++;
1126
1127 if( phys->lift_frames >= 8 )
1128 phys->in_air = 1;
1129 }
1130 else
1131 {
1132 for( int i=0; i<len; i++ )
1133 v3_add( surface_avg, manifold[i].n, surface_avg );
1134 v3_normalize( surface_avg );
1135
1136 if( v3_dot( phys->rb.v, surface_avg ) > 0.7f )
1137 {
1138 phys->lift_frames ++;
1139
1140 if( phys->lift_frames >= 8 )
1141 phys->in_air = 1;
1142 }
1143 else
1144 {
1145 phys->in_air = 0;
1146 phys->lift_frames = 0;
1147 v3f projected, axis;
1148
1149 float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
1150 v3_muladds( phys->rb.v, phys->rb.up, DOWNFORCE, phys->rb.v );
1151
1152 float d = v3_dot( phys->rb.forward, surface_avg );
1153 v3_muladds( surface_avg, phys->rb.forward, -d, projected );
1154 v3_normalize( projected );
1155
1156 float angle = v3_dot( phys->rb.up, projected );
1157 v3_cross( phys->rb.up, projected, axis );
1158
1159 #if 0
1160 v3f p0, p1;
1161 v3_add( phys->rb.co, projected, p0 );
1162 v3_add( phys->rb.co, phys->rb.up, p1 );
1163 vg_line( phys->rb.co, p0, 0xff00ff00 );
1164 vg_line( phys->rb.co, p1, 0xff000fff );
1165 #endif
1166
1167 if( fabsf(angle) < 0.999f )
1168 {
1169 v4f correction;
1170 q_axis_angle( correction, axis,
1171 acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
1172 q_mul( correction, phys->rb.q, phys->rb.q );
1173 }
1174 }
1175 }
1176
1177 if( !was_in_air && phys->in_air )
1178 player_start_air();
1179 }
1180
1181 VG_STATIC void player_collision_response( rb_ct *manifold, int len )
1182 {
1183 struct player_phys *phys = &player.phys;
1184
1185 /*
1186 * EXPERIMENTAL
1187 * ===============================================
1188 */
1189
1190 #if 0
1191 player.normal_pressure = v3_dot( phys->rb.up, phys->rb.v );
1192
1193 {
1194 float ideal = 1.0f-player.input_grab->axis.value,
1195 diff = phys->spring - ideal,
1196 Fspring = -k_cog_spring_lat * diff,
1197 Fdamp = -k_cog_damp * phys->springv,
1198 F = (Fspring + Fdamp) * k_rb_delta;
1199
1200 phys->springv += F;
1201 phys->spring += phys->springv * k_rb_delta;
1202
1203 if( phys->springv > 0.0f )
1204 v3_muladds( phys->rb.v, phys->rb.up, F*k_cog_spring_vert, phys->rb.v );
1205
1206 if( phys->in_air )
1207 player.normal_pressure = 0.0f;
1208 else
1209 player.normal_pressure = v3_dot( phys->rb.up, phys->rb.v );
1210 }
1211 #endif
1212
1213 if( player.input_grab->axis.value > 0.5f )
1214 {
1215 if( !phys->in_air )
1216 {
1217 /* Throw */
1218 v3_muls( phys->rb.up, k_mmthrow_scale, phys->throw_v );
1219 }
1220 }
1221 else
1222 {
1223 /* Collect */
1224 float doty = v3_dot( phys->rb.up, phys->throw_v );
1225
1226 v3f Fl, Fv;
1227 v3_muladds( phys->throw_v, phys->rb.up, -doty, Fl );
1228
1229 if( !phys->in_air )
1230 {
1231 v3_muladds( phys->rb.v, Fl, k_mmcollect_lat, phys->rb.v );
1232 v3_muladds( phys->throw_v, Fl, -k_mmcollect_lat, phys->throw_v );
1233 }
1234
1235 v3_muls( phys->rb.up, -doty, Fv );
1236 v3_muladds( phys->rb.v, Fv, k_mmcollect_vert, phys->rb.v );
1237 v3_muladds( phys->throw_v, Fv, k_mmcollect_vert, phys->throw_v );
1238
1239 v3_copy( Fl, player.debug_mmcollect_lat );
1240 v3_copy( Fv, player.debug_mmcollect_vert );
1241 }
1242
1243 /* Decay */
1244 if( v3_length2( phys->throw_v ) > 0.0001f )
1245 {
1246 v3f dir;
1247 v3_copy( phys->throw_v, dir );
1248 v3_normalize( dir );
1249
1250 float max = v3_dot( dir, phys->throw_v ),
1251 amt = vg_minf( k_mmdecay * k_rb_delta, max );
1252
1253 v3_muladds( phys->throw_v, dir, -amt, phys->throw_v );
1254 }
1255
1256
1257 /* TODO: RElocate */
1258 {
1259
1260 v3f ideal_cog, ideal_diff;
1261 v3_muladds( phys->rb.co, phys->rb.up,
1262 1.0f-player.input_grab->axis.value, ideal_cog );
1263 v3_sub( ideal_cog, phys->cog, ideal_diff );
1264
1265 /* Apply velocities */
1266 v3f rv;
1267 v3_sub( phys->rb.v, phys->cog_v, rv );
1268
1269 v3f F;
1270 v3_muls( ideal_diff, -k_cog_spring * k_rb_rate, F );
1271 v3_muladds( F, rv, -k_cog_damp * k_rb_rate, F );
1272
1273 float ra = k_cog_mass_ratio,
1274 rb = 1.0f-k_cog_mass_ratio;
1275
1276 v3_muladds( phys->cog_v, F, -rb, phys->cog_v );
1277 }
1278
1279 /*
1280 * EXPERIMENTAL
1281 * ===============================================
1282 */
1283
1284 for( int j=0; j<10; j++ )
1285 {
1286 for( int i=0; i<len; i++ )
1287 {
1288 struct contact *ct = &manifold[i];
1289
1290 v3f dv, delta;
1291 v3_sub( ct->co, phys->rb.co, delta );
1292 v3_cross( phys->rb.w, delta, dv );
1293 v3_add( phys->rb.v, dv, dv );
1294
1295 float vn = -v3_dot( dv, ct->n );
1296 vn += ct->bias;
1297
1298 float temp = ct->norm_impulse;
1299 ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
1300 vn = ct->norm_impulse - temp;
1301
1302 v3f impulse;
1303 v3_muls( ct->n, vn, impulse );
1304
1305 if( fabsf(v3_dot( impulse, phys->rb.forward )) > 10.0f ||
1306 fabsf(v3_dot( impulse, phys->rb.up )) > 50.0f )
1307 {
1308 player_kill();
1309 return;
1310 }
1311
1312 v3_add( impulse, phys->rb.v, phys->rb.v );
1313 v3_cross( delta, impulse, impulse );
1314
1315 /*
1316 * W Impulses are limited to the Y and X axises, we don't really want
1317 * roll angular velocities being included.
1318 *
1319 * Can also tweak the resistance of each axis here by scaling the wx,wy
1320 * components.
1321 */
1322
1323 float wy = v3_dot( phys->rb.up, impulse ) * 0.8f,
1324 wx = v3_dot( phys->rb.right, impulse )*1.0f;
1325
1326 v3_muladds( phys->rb.w, phys->rb.up, wy, phys->rb.w );
1327 v3_muladds( phys->rb.w, phys->rb.right, wx, phys->rb.w );
1328 }
1329 }
1330
1331 /* early integrate this */
1332 phys->cog_v[1] += -9.8f * k_rb_delta;
1333 v3_muladds( phys->cog, phys->cog_v, k_rb_delta, phys->cog );
1334 }
1335
1336 VG_STATIC void player_save_frame(void)
1337 {
1338 player.phys_gate_frame = player.phys;
1339 }
1340
1341 VG_STATIC void player_restore_frame(void)
1342 {
1343 player.phys = player.phys_gate_frame;
1344 rb_update_transform( &player.phys.rb );
1345 }
1346
1347 VG_STATIC void player_integrate(void)
1348 {
1349 struct player_phys *phys = &player.phys;
1350 v3_sub( phys->rb.v, phys->v_last, phys->a );
1351 v3_muls( phys->a, 1.0f/VG_TIMESTEP_FIXED, phys->a );
1352 v3_copy( phys->rb.v, phys->v_last );
1353
1354 apply_gravity( phys->rb.v, VG_TIMESTEP_FIXED );
1355 v3_muladds( phys->rb.co, phys->rb.v, VG_TIMESTEP_FIXED, phys->rb.co );
1356 }
1357
1358 VG_STATIC void player_do_motion(void)
1359 {
1360 struct player_phys *phys = &player.phys;
1361
1362 if( world.water.enabled )
1363 {
1364 if( (phys->rb.co[1] < 0.0f) && !player.is_dead )
1365 {
1366 audio_lock();
1367 audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
1368 audio_player_set_position( &audio_player_extra, phys->rb.co );
1369 audio_player_set_vol( &audio_player_extra, 20.0f );
1370 audio_player_playclip( &audio_player_extra, &audio_splash );
1371 audio_unlock();
1372
1373 player_kill();
1374 }
1375 }
1376
1377 v3f prevco;
1378 v3_copy( phys->rb.co, prevco );
1379
1380 if( phys->on_board )
1381 {
1382 rb_ct manifold[72];
1383
1384 player_regular_collider_configuration();
1385 int len = player_update_collision_manifold( manifold );
1386 int grind_col = player_update_grind_collision( &manifold[len] );
1387
1388 static int _grind_col_pre = 0;
1389
1390 if( grind_col )
1391 {
1392 phys->grind = 1;
1393 v3f up = { 0.0f, 1.0f, 0.0f };
1394 float angle = v3_dot( phys->rb.up, up );
1395
1396 if( fabsf(angle) < 0.99f )
1397 {
1398 v3f axis;
1399 v3_cross( phys->rb.up, up, axis );
1400
1401 v4f correction;
1402 q_axis_angle( correction, axis,
1403 VG_TIMESTEP_FIXED * 10.0f * acosf(angle) );
1404 q_mul( correction, phys->rb.q, phys->rb.q );
1405 }
1406
1407 float const DOWNFORCE = -k_downforce*1.2f*VG_TIMESTEP_FIXED;
1408 v3_muladds( phys->rb.v, manifold[len].n, DOWNFORCE, phys->rb.v );
1409 m3x3_identity( phys->vr );
1410 m3x3_identity( phys->vr_pstep );
1411
1412 if( !_grind_col_pre )
1413 {
1414 audio_lock();
1415 audio_player_set_flags( &audio_player_extra,
1416 AUDIO_FLAG_SPACIAL_3D );
1417 audio_player_set_position( &audio_player_extra, phys->rb.co );
1418 audio_player_set_vol( &audio_player_extra, 20.0f );
1419 audio_player_playclip( &audio_player_extra, &audio_board[5] );
1420 audio_unlock();
1421 }
1422 }
1423 else
1424 {
1425 phys->grind = 0;
1426 player_adhere_ground( manifold, len );
1427
1428 if( _grind_col_pre )
1429 {
1430 audio_lock();
1431 audio_player_set_flags( &audio_player_extra,
1432 AUDIO_FLAG_SPACIAL_3D );
1433 audio_player_set_position( &audio_player_extra, phys->rb.co );
1434 audio_player_set_vol( &audio_player_extra, 20.0f );
1435 audio_player_playclip( &audio_player_extra, &audio_board[6] );
1436 audio_unlock();
1437 }
1438 }
1439
1440 _grind_col_pre = grind_col;
1441
1442 rb_presolve_contacts( manifold, len+ VG_MAX(0,grind_col) );
1443 player_collision_response( manifold, len+ VG_MAX(0,grind_col) );
1444
1445 player_physics_control_passive();
1446
1447 if( grind_col )
1448 {
1449 phys->in_air = 0;
1450 player_physics_control_grind();
1451 }
1452 else
1453 {
1454 if( phys->in_air )
1455 player_physics_control_air();
1456 else
1457 player_physics_control();
1458 }
1459
1460 player_integrate();
1461 }
1462 else
1463 player_walk_physics();
1464
1465
1466 /* Real angular velocity integration */
1467 v3_lerp( phys->rb.w, (v3f){0.0f,0.0f,0.0f}, 0.125f*0.5f, phys->rb.w );
1468 if( v3_length2( phys->rb.w ) > 0.0f )
1469 {
1470 v4f rotation;
1471 v3f axis;
1472 v3_copy( phys->rb.w, axis );
1473
1474 float mag = v3_length( axis );
1475 v3_divs( axis, mag, axis );
1476 q_axis_angle( rotation, axis, mag*k_rb_delta );
1477 q_mul( rotation, phys->rb.q, phys->rb.q );
1478 }
1479
1480 /* Faux angular velocity */
1481 v4f rotate;
1482
1483 float lerpq = phys->in_air? 0.04f: 0.3f;
1484 phys->siY = vg_lerpf( phys->siY, phys->iY, lerpq );
1485
1486 q_axis_angle( rotate, phys->rb.up, phys->siY );
1487 q_mul( rotate, phys->rb.q, phys->rb.q );
1488 phys->iY = 0.0f;
1489
1490 /*
1491 * Gate intersection, by tracing a line over the gate planes
1492 */
1493 for( int i=0; i<world.gate_count; i++ )
1494 {
1495 struct route_gate *rg = &world.gates[i];
1496 teleport_gate *gate = &rg->gate;
1497
1498 if( gate_intersect( gate, phys->rb.co, prevco ) )
1499 {
1500 m4x3_mulv( gate->transport, phys->rb.co, phys->rb.co );
1501 m4x3_mulv( gate->transport, phys->cog, phys->cog );
1502 m3x3_mulv( gate->transport, phys->cog_v, phys->cog_v );
1503 m3x3_mulv( gate->transport, phys->rb.v, phys->rb.v );
1504 m3x3_mulv( gate->transport, phys->vl, phys->vl );
1505 m3x3_mulv( gate->transport, phys->v_last, phys->v_last );
1506 m3x3_mulv( gate->transport, phys->m, phys->m );
1507 m3x3_mulv( gate->transport, phys->bob, phys->bob );
1508
1509 /* Pre-emptively edit the camera matrices so that the motion vectors
1510 * are correct */
1511 m4x3f transport_i;
1512 m4x4f transport_4;
1513 m4x3_invert_affine( gate->transport, transport_i );
1514 m4x3_expand( transport_i, transport_4 );
1515 m4x4_mul( main_camera.mtx.pv, transport_4, main_camera.mtx.pv );
1516 m4x4_mul( main_camera.mtx.v, transport_4, main_camera.mtx.v );
1517
1518 v4f transport_rotation;
1519 m3x3_q( gate->transport, transport_rotation );
1520 q_mul( transport_rotation, phys->rb.q, phys->rb.q );
1521
1522 world_routes_activate_gate( i );
1523
1524 if( !phys->on_board )
1525 {
1526 v3f fwd_dir = {cosf(player.angles[0]),
1527 0.0f,
1528 sinf(player.angles[0])};
1529 m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
1530
1531 player.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
1532 }
1533
1534 player.rewind_length = 0;
1535 player.rewind_total_length = 0.0f;
1536 player.rewind_incrementer = 10000;
1537 player_save_frame();
1538
1539 audio_lock();
1540 audio_play_oneshot( &audio_gate_pass, 1.0f );
1541 audio_unlock();
1542 break;
1543 }
1544 }
1545
1546 rb_update_transform( &phys->rb );
1547 }
1548
1549 VG_STATIC void player_freecam(void)
1550 {
1551 player_mouseview();
1552
1553 float movespeed = fc_speed * VG_TIMESTEP_FIXED;
1554 v3f lookdir = { 0.0f, 0.0f, -1.0f },
1555 sidedir = { 1.0f, 0.0f, 0.0f };
1556
1557 m3x3_mulv( main_camera.transform, lookdir, lookdir );
1558 m3x3_mulv( main_camera.transform, sidedir, sidedir );
1559
1560 static v3f move_vel = { 0.0f, 0.0f, 0.0f };
1561
1562 v2f steer = { player.input_js1h->axis.value,
1563 player.input_js1v->axis.value };
1564
1565 v3_muladds( move_vel, sidedir, movespeed*steer[0], move_vel );
1566 v3_muladds( move_vel, lookdir, -movespeed*steer[1], move_vel );
1567
1568 v3_muls( move_vel, 0.7f, move_vel );
1569 v3_add( move_vel, player.camera_pos, player.camera_pos );
1570 }
1571
1572 VG_STATIC int kill_player( int argc, char const *argv[] )
1573 {
1574 player_kill();
1575 return 0;
1576 }
1577
1578 VG_STATIC int reset_player( int argc, char const *argv[] )
1579 {
1580 struct player_phys *phys = &player.phys;
1581 struct respawn_point *rp = NULL, *r;
1582
1583 if( argc == 1 )
1584 {
1585 for( int i=0; i<world.spawn_count; i++ )
1586 {
1587 r = &world.spawns[i];
1588 if( !strcmp( r->name, argv[0] ) )
1589 {
1590 rp = r;
1591 break;
1592 }
1593 }
1594
1595 if( !rp )
1596 vg_warn( "No spawn named '%s'\n", argv[0] );
1597 }
1598
1599 if( !rp )
1600 {
1601 float min_dist = INFINITY;
1602
1603 for( int i=0; i<world.spawn_count; i++ )
1604 {
1605 r = &world.spawns[i];
1606 float d = v3_dist2( r->co, phys->rb.co );
1607
1608 vg_info( "Dist %s : %f\n", r->name, d );
1609 if( d < min_dist )
1610 {
1611 min_dist = d;
1612 rp = r;
1613 }
1614 }
1615 }
1616
1617 if( !rp )
1618 {
1619 vg_error( "No spawn found\n" );
1620 vg_info( "Player position: %f %f %f\n", player.phys.rb.co[0],
1621 player.phys.rb.co[1],
1622 player.phys.rb.co[2] );
1623 vg_info( "Player velocity: %f %f %f\n", player.phys.rb.v[0],
1624 player.phys.rb.v[1],
1625 player.phys.rb.v[2] );
1626
1627 if( !world.spawn_count )
1628 return 0;
1629
1630 rp = &world.spawns[0];
1631 }
1632
1633 player.is_dead = 0;
1634
1635 m3x3f the_long_way;
1636 q_m3x3( rp->q, the_long_way );
1637
1638 v3f delta = {1.0f,0.0f,0.0f};
1639 m3x3_mulv( the_long_way, delta, delta );
1640
1641 if( !freecam )
1642 {
1643 player.angles[0] = atan2f( delta[0], -delta[2] );
1644 player.angles[1] = -asinf( delta[1] );
1645 }
1646
1647 v4_copy( rp->q, phys->rb.q );
1648 v3_copy( rp->co, phys->rb.co );
1649 v3_zero( phys->rb.v );
1650
1651 phys->vswitch = 1.0f;
1652 phys->slip_last = 0.0f;
1653 phys->in_air = 1;
1654 phys->on_board = 0;
1655 m3x3_identity( phys->vr );
1656
1657 player.mdl.shoes[0] = 1;
1658 player.mdl.shoes[1] = 1;
1659
1660 rb_update_transform( &phys->rb );
1661
1662 v3_add( phys->rb.up, phys->rb.co, phys->cog );
1663 v3_zero( phys->cog_v );
1664
1665 player_save_frame();
1666 return 1;
1667 }
1668
1669 VG_STATIC void reset_player_poll( int argc, char const *argv[] )
1670 {
1671 if( argc == 1 )
1672 {
1673 for( int i=0; i<world.spawn_count; i++ )
1674 {
1675 struct respawn_point *r = &world.spawns[i];
1676
1677 console_suggest_score_text( r->name, argv[argc-1], 0 );
1678 }
1679 }
1680 }
1681
1682 VG_STATIC void player_physics_gui(void)
1683 {
1684 return;
1685
1686 vg_uictx.cursor[0] = 0;
1687 vg_uictx.cursor[1] = vg.window_y - 128;
1688 vg_uictx.cursor[3] = 14;
1689 ui_fill_x();
1690
1691 char buf[128];
1692
1693 snprintf( buf, 127, "v: %6.3f %6.3f %6.3f\n", player.phys.rb.v[0],
1694 player.phys.rb.v[1],
1695 player.phys.rb.v[2] );
1696
1697 ui_text( vg_uictx.cursor, buf, 1, 0 );
1698 vg_uictx.cursor[1] += 14;
1699
1700
1701 snprintf( buf, 127, "a: %6.3f %6.3f %6.3f (%6.3f)\n", player.phys.a[0],
1702 player.phys.a[1],
1703 player.phys.a[2],
1704 v3_length(player.phys.a));
1705 ui_text( vg_uictx.cursor, buf, 1, 0 );
1706 vg_uictx.cursor[1] += 14;
1707
1708 float normal_acceleration = v3_dot( player.phys.a, player.phys.rb.up );
1709 snprintf( buf, 127, "Normal acceleration: %6.3f\n", normal_acceleration );
1710
1711 ui_text( vg_uictx.cursor, buf, 1, 0 );
1712 vg_uictx.cursor[1] += 14;
1713
1714 snprintf( buf, 127, "Normal Pressure: %6.3f\n", player.normal_pressure );
1715 ui_text( vg_uictx.cursor, buf, 1, 0 );
1716 vg_uictx.cursor[1] += 14;
1717
1718
1719 }
1720
1721 #endif /* PLAYER_PHYSICS_H */