52c51da75257e6a4f92b41d4f48879d53bf4b67b
[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 /*
18 * Called when launching into the air to predict and adjust trajectories
19 */
20 VG_STATIC void player_start_air(void)
21 {
22 struct player_phys *phys = &player.phys;
23
24 float pstep = VG_TIMESTEP_FIXED * 10.0f;
25 float best_velocity_delta = -9999.9f;
26 float k_bias = 0.96f;
27
28 v3f axis;
29 v3_cross( phys->rb.up, phys->rb.v, axis );
30 v3_normalize( axis );
31 player.land_log_count = 0;
32
33 m3x3_identity( phys->vr );
34
35 for( int m=-3;m<=12; m++ )
36 {
37 float vmod = ((float)m / 15.0f)*0.09f;
38
39 v3f pco, pco1, pv;
40 v3_copy( phys->rb.co, pco );
41 v3_muls( phys->rb.v, k_bias, pv );
42
43 /*
44 * Try different 'rotations' of the velocity to find the best possible
45 * landing normal. This conserves magnitude at the expense of slightly
46 * unrealistic results
47 */
48
49 m3x3f vr;
50 v4f vr_q;
51
52 q_axis_angle( vr_q, axis, vmod );
53 q_m3x3( vr_q, vr );
54
55 m3x3_mulv( vr, pv, pv );
56 v3_muladds( pco, pv, pstep, pco );
57
58 for( int i=0; i<50; i++ )
59 {
60 v3_copy( pco, pco1 );
61 apply_gravity( pv, pstep );
62
63 m3x3_mulv( vr, pv, pv );
64 v3_muladds( pco, pv, pstep, pco );
65
66 ray_hit contact;
67 v3f vdir;
68
69 v3_sub( pco, pco1, vdir );
70 contact.dist = v3_length( vdir );
71 v3_divs( vdir, contact.dist, vdir);
72
73 if( ray_world( pco1, vdir, &contact ))
74 {
75 float land_delta = v3_dot( pv, contact.normal );
76 u32 scolour = (u8)(vg_minf(-land_delta * 2.0f, 255.0f));
77
78 /* Bias prediction towords ramps */
79 if( ray_hit_material( &contact )->info.flags
80 & k_material_flag_skate_surface )
81 {
82 land_delta *= 0.1f;
83 scolour |= 0x0000a000;
84 }
85
86 if( (land_delta < 0.0f) && (land_delta > best_velocity_delta) )
87 {
88 best_velocity_delta = land_delta;
89
90 v3_copy( contact.pos, player.land_target );
91
92 m3x3_copy( vr, phys->vr_pstep );
93 q_axis_angle( vr_q, axis, vmod*0.1f );
94 q_m3x3( vr_q, phys->vr );
95 }
96
97 v3_copy( contact.pos,
98 player.land_target_log[player.land_log_count] );
99 player.land_target_colours[player.land_log_count] =
100 0xff000000 | scolour;
101
102 player.land_log_count ++;
103
104 break;
105 }
106 }
107 }
108 }
109
110
111 VG_STATIC void player_physics_control_passive(void)
112 {
113 struct player_phys *phys = &player.phys;
114 float grabt = player.input_grab->axis.value;
115
116 if( grabt > 0.5f )
117 {
118 v2_muladds( phys->grab_mouse_delta, vg.mouse_delta, 0.02f,
119 phys->grab_mouse_delta );
120 v2_normalize_clamp( phys->grab_mouse_delta );
121 }
122 else
123 v2_zero( phys->grab_mouse_delta );
124
125 phys->grab = vg_lerpf( phys->grab, grabt, 0.14f );
126 player.phys.pushing = 0.0f;
127
128 if( !phys->jump_charge || phys->in_air )
129 {
130 phys->jump -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
131 }
132
133 phys->jump_charge = 0;
134 phys->jump = vg_clampf( phys->jump, 0.0f, 1.0f );
135 }
136
137 /*
138 * Main friction interface model
139 */
140 VG_STATIC void player_physics_control(void)
141 {
142 struct player_phys *phys = &player.phys;
143
144 /*
145 * Computing localized friction forces for controlling the character
146 * Friction across X is significantly more than Z
147 */
148
149 v3f vel;
150 m3x3_mulv( phys->rb.to_local, phys->rb.v, vel );
151 float slip = 0.0f;
152
153 if( fabsf(vel[2]) > 0.01f )
154 slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
155
156 if( fabsf( slip ) > 1.2f )
157 slip = vg_signf( slip ) * 1.2f;
158 phys->slip = slip;
159 phys->reverse = -vg_signf(vel[2]);
160
161 float substep = VG_TIMESTEP_FIXED * 0.2f;
162 float fwd_resistance = k_friction_resistance;
163
164 for( int i=0; i<5; i++ )
165 {
166 vel[2] = stable_force( vel[2],vg_signf(vel[2]) * -fwd_resistance*substep);
167 vel[0] = stable_force( vel[0],vg_signf(vel[0]) * -k_friction_lat*substep);
168 }
169
170 if( player.input_jump->button.value )
171 {
172 phys->jump += VG_TIMESTEP_FIXED * k_jump_charge_speed;
173
174 if( !phys->jump_charge )
175 phys->jump_dir = phys->reverse > 0.0f? 1: 0;
176
177 phys->jump_charge = 1;
178 }
179
180 static int push_thresh_last = 0;
181 float push = player.input_push->button.value;
182 int push_thresh = push>0.15f? 1: 0;
183
184 if( push_thresh && !push_thresh_last )
185 player.phys.start_push = vg.time;
186
187 push_thresh_last = push_thresh;
188
189 if( !player.input_jump->button.value && push_thresh )
190 {
191 player.phys.pushing = 1.0f;
192 player.phys.push_time = vg.time - player.phys.start_push;
193
194 float cycle_time = player.phys.push_time*k_push_cycle_rate,
195 amt = k_push_accel * (sinf(cycle_time)*0.5f+0.5f)*VG_TIMESTEP_FIXED,
196 current = v3_length( vel ),
197 new_vel = vg_minf( current + amt, k_max_push_speed );
198
199 new_vel -= vg_minf(current, k_max_push_speed);
200 vel[2] -= new_vel * phys->reverse;
201 }
202
203 m3x3_mulv( phys->rb.to_world, vel, phys->rb.v );
204
205 float input = player.input_js1h->axis.value,
206 grab = player.input_grab->axis.value,
207 steer = input * (1.0f-(phys->jump+grab)*0.4f),
208 steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
209
210 phys->iY -= steer_scaled * VG_TIMESTEP_FIXED;
211
212 if( !phys->jump_charge && phys->jump > 0.2f )
213 {
214 v3f jumpdir;
215
216 /* Launch more up if alignment is up else improve velocity */
217 float aup = fabsf(v3_dot( (v3f){0.0f,1.0f,0.0f}, phys->rb.up )),
218 mod = 0.5f,
219 dir = mod + aup*(1.0f-mod);
220
221 v3_copy( phys->rb.v, jumpdir );
222 v3_normalize( jumpdir );
223 v3_muls( jumpdir, 1.0f-dir, jumpdir );
224 v3_muladds( jumpdir, phys->rb.up, dir, jumpdir );
225 v3_normalize( jumpdir );
226
227 float force = k_jump_force*phys->jump;
228 v3_muladds( phys->rb.v, jumpdir, force, phys->rb.v );
229 phys->jump = 0.0f;
230
231 player.jump_time = vg.time;
232
233 /* TODO: Move to audio file */
234 audio_lock();
235 audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
236 audio_player_set_position( &audio_player_extra, phys->rb.co );
237 audio_player_set_vol( &audio_player_extra, 20.0f );
238 audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
239 audio_unlock();
240 }
241 }
242
243 VG_STATIC void player_physics_control_grind(void)
244 {
245 struct player_phys *phys = &player.phys;
246 v2f steer = { player.input_js1h->axis.value,
247 player.input_js1v->axis.value };
248
249 float l2 = v2_length2( steer );
250 if( l2 > 1.0f )
251 v2_muls( steer, 1.0f/sqrtf(l2), steer );
252
253 phys->iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
254
255 float iX = steer[1] * phys->reverse * k_steer_air * VG_TIMESTEP_FIXED;
256
257 static float siX = 0.0f;
258 siX = vg_lerpf( siX, iX, k_steer_air_lerp );
259
260 v4f rotate;
261 q_axis_angle( rotate, phys->rb.right, siX );
262 q_mul( rotate, phys->rb.q, phys->rb.q );
263
264 phys->slip = 0.0f;
265 }
266
267 /*
268 * Air control, no real physics
269 */
270 VG_STATIC void player_physics_control_air(void)
271 {
272 struct player_phys *phys = &player.phys;
273
274 m3x3_mulv( phys->vr, phys->rb.v, phys->rb.v );
275 vg_line_cross( player.land_target, 0xff0000ff, 0.25f );
276
277 ray_hit hit;
278
279 /*
280 * Prediction
281 */
282 float pstep = VG_TIMESTEP_FIXED * 10.0f;
283
284 v3f pco, pco1, pv;
285 v3_copy( phys->rb.co, pco );
286 v3_copy( phys->rb.v, pv );
287
288 float time_to_impact = 0.0f;
289 float limiter = 1.0f;
290
291 for( int i=0; i<50; i++ )
292 {
293 v3_copy( pco, pco1 );
294 m3x3_mulv( phys->vr_pstep, pv, pv );
295 apply_gravity( pv, pstep );
296 v3_muladds( pco, pv, pstep, pco );
297
298 ray_hit contact;
299 v3f vdir;
300
301 v3_sub( pco, pco1, vdir );
302 contact.dist = v3_length( vdir );
303 v3_divs( vdir, contact.dist, vdir);
304
305 float orig_dist = contact.dist;
306 if( ray_world( pco1, vdir, &contact ))
307 {
308 float angle = v3_dot( phys->rb.up, contact.normal );
309 v3f axis;
310 v3_cross( phys->rb.up, contact.normal, axis );
311
312 time_to_impact += (contact.dist/orig_dist)*pstep;
313 limiter = vg_minf( 5.0f, time_to_impact )/5.0f;
314 limiter = 1.0f-limiter;
315 limiter *= limiter;
316 limiter = 1.0f-limiter;
317
318 if( fabsf(angle) < 0.99f )
319 {
320 v4f correction;
321 q_axis_angle( correction, axis,
322 acosf(angle)*(1.0f-limiter)*3.0f*VG_TIMESTEP_FIXED );
323 q_mul( correction, phys->rb.q, phys->rb.q );
324 }
325
326 vg_line_cross( contact.pos, 0xffff0000, 0.25f );
327 break;
328 }
329 time_to_impact += pstep;
330 }
331
332 v2f steer = { player.input_js1h->axis.value,
333 player.input_js1v->axis.value };
334
335 float l2 = v2_length2( steer );
336 if( l2 > 1.0f )
337 v2_muls( steer, 1.0f/sqrtf(l2), steer );
338
339 phys->iY -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
340
341 float iX = steer[1] *
342 phys->reverse * k_steer_air * limiter * VG_TIMESTEP_FIXED;
343
344 static float siX = 0.0f;
345 siX = vg_lerpf( siX, iX, k_steer_air_lerp );
346
347 v4f rotate;
348 q_axis_angle( rotate, phys->rb.right, siX );
349 q_mul( rotate, phys->rb.q, phys->rb.q );
350
351 #if 0
352 v2f target = {0.0f,0.0f};
353 v2_muladds( target, (v2f){ vg_get_axis("grabh"), vg_get_axis("grabv") },
354 phys->grab, target );
355 #endif
356 }
357
358 VG_STATIC void player_walk_update_collision(void)
359 {
360 struct player_phys *phys = &player.phys;
361 float h0 = 0.3f,
362 h1 = 0.9f;
363
364 rigidbody *rbf = &player.collide_front,
365 *rbb = &player.collide_back;
366
367 v3_add( phys->rb.co, (v3f){0.0f,h0,0.0f}, rbf->co );
368 v3_add( phys->rb.co, (v3f){0.0f,h1,0.0f}, rbb->co );
369 v3_copy( rbf->co, rbf->to_world[3] );
370 v3_copy( rbb->co, rbb->to_world[3] );
371 m4x3_invert_affine( rbf->to_world, rbf->to_local );
372 m4x3_invert_affine( rbb->to_world, rbb->to_local );
373
374 rb_update_bounds( rbf );
375 rb_update_bounds( rbb );
376 }
377
378 VG_STATIC void player_integrate(void);
379 /*
380 * Entire Walking physics model
381 * TODO: sleep when under certain velotiy
382 */
383 VG_STATIC void player_walk_physics(void)
384 {
385 struct player_phys *phys = &player.phys;
386 rigidbody *rbf = &player.collide_front,
387 *rbb = &player.collide_back;
388
389 m3x3_identity( player.collide_front.to_world );
390 m3x3_identity( player.collide_back.to_world );
391
392 v3_zero( phys->rb.w );
393 q_axis_angle( phys->rb.q, (v3f){0.0f,1.0f,0.0f}, -player.angles[0] );
394
395 rb_ct manifold[64];
396 int len;
397
398 v3f forward_dir = { sinf(player.angles[0]),0.0f,-cosf(player.angles[0]) };
399 v3f right_dir = { -forward_dir[2], 0.0f, forward_dir[0] };
400
401 v2f walk = { player.input_walkh->axis.value,
402 player.input_walkv->axis.value };
403
404 if( v2_length2(walk) > 0.001f )
405 v2_normalize_clamp( walk );
406
407 if( phys->in_air )
408 {
409 player_walk_update_collision();
410 rb_debug( rbf, 0xff0000ff );
411 rb_debug( rbb, 0xff0000ff );
412
413 /* allow player to accelerate a bit */
414 v3f walk_3d;
415 v3_muls( forward_dir, walk[1], walk_3d );
416 v3_muladds( walk_3d, right_dir, walk[0], walk_3d );
417
418 float current_vel = fabsf(v3_dot( walk_3d, phys->rb.v )),
419 new_vel = current_vel + VG_TIMESTEP_FIXED*k_air_accelerate,
420 clamped_new = vg_clampf( new_vel, 0.0f, k_walkspeed ),
421 vel_diff = vg_maxf( 0.0f, clamped_new - current_vel );
422
423 v3_muladds( phys->rb.v, right_dir, walk[0] * vel_diff, phys->rb.v );
424 v3_muladds( phys->rb.v, forward_dir, walk[1] * vel_diff, phys->rb.v );
425
426
427 len = 0;
428 len += rb_sphere_scene( rbf, &world.rb_geo, manifold+len );
429 len += rb_sphere_scene( rbb, &world.rb_geo, manifold+len );
430 rb_presolve_contacts( manifold, len );
431
432 for( int i=0; i<len; i++ )
433 {
434 struct contact *ct = &manifold[i];
435 if( v3_dot( ct->n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f )
436 phys->in_air = 0;
437 }
438
439 for( int j=0; j<5; j++ )
440 {
441 for( int i=0; i<len; i++ )
442 {
443 struct contact *ct = &manifold[i];
444
445 /*normal */
446 float vn = -v3_dot( phys->rb.v, ct->n );
447 vn += ct->bias;
448
449 float temp = ct->norm_impulse;
450 ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
451 vn = ct->norm_impulse - temp;
452
453 v3f impulse;
454 v3_muls( ct->n, vn, impulse );
455
456 v3_add( impulse, phys->rb.v, phys->rb.v );
457
458 /* friction */
459 for( int j=0; j<2; j++ )
460 {
461 float f = k_friction * ct->norm_impulse,
462 vt = v3_dot( phys->rb.v, ct->t[j] ),
463 lambda = -vt;
464
465 float temp = ct->tangent_impulse[j];
466 ct->tangent_impulse[j] = vg_clampf( temp + lambda, -f, f );
467 lambda = ct->tangent_impulse[j] - temp;
468
469 v3_muladds( phys->rb.v, ct->t[j], lambda, phys->rb.v );
470 }
471 }
472 }
473
474 player_integrate();
475 }
476 else
477 {
478 player.walk = v2_length( walk );
479
480 if( player.input_walk->button.value )
481 v2_muls( walk, 0.5f, walk );
482
483 v2_muls( walk, k_walkspeed * VG_TIMESTEP_FIXED, walk );
484
485 v3f walk_apply;
486 v3_zero( walk_apply );
487
488 /* Do XY translation */
489 v3_muladds( walk_apply, right_dir, walk[0], walk_apply );
490 v3_muladds( walk_apply, forward_dir, walk[1], walk_apply );
491 v3_add( walk_apply, phys->rb.co, phys->rb.co );
492 v3_divs( walk_apply, VG_TIMESTEP_FIXED, phys->rb.v );
493
494 /* Directly resolve collisions */
495 player_walk_update_collision();
496 rb_debug( rbf, 0xffffff00 );
497 rb_debug( rbb, 0xffffff00 );
498
499 len = 0;
500 len += rb_sphere_scene( rbf, &world.rb_geo, manifold+len );
501 len += rb_sphere_scene( rbb, &world.rb_geo, manifold+len );
502
503 v3f dt;
504 v3_zero( dt );
505 for( int j=0; j<3; j++ )
506 {
507 for( int i=0; i<len; i++ )
508 {
509 struct contact *ct = &manifold[i];
510
511 float p = vg_maxf( 0.0f, ct->p - 0.00f ),
512 cur = vg_clampf( v3_dot( ct->n, dt ), 0.0f, p );
513 v3_muladds( dt, ct->n, (p - cur) * 0.333333333f, dt );
514 }
515 }
516 v3_add( dt, phys->rb.co, phys->rb.co );
517
518 if( len )
519 {
520 struct world_material *surface_mat = world_contact_material(manifold);
521 player.surface_prop = surface_mat->info.surface_prop;
522 }
523
524 /* jump */
525 if( player.input_jump->button.value )
526 {
527 phys->rb.v[1] = 5.0f;
528 phys->in_air = 1;
529 return;
530 }
531
532 /* if we've put us in the air, step down slowly */
533 phys->in_air = 1;
534 float max_dist = 0.3f,
535 start_y = phys->rb.co[1];
536
537 for( int j=0; j<8; j++ )
538 {
539 for( int i=0; i<len; i++ )
540 {
541 struct contact *ct = &manifold[i];
542 if( v3_dot( ct->n, (v3f){0.0f,1.0f,0.0f} ) > 0.5f )
543 {
544 phys->in_air = 0;
545 if( j == 0 )
546 return;
547
548 v3f dt;
549 v3_zero( dt );
550 for( int j=0; j<3; j++ )
551 {
552 for( int i=0; i<len; i++ )
553 {
554 struct contact *ct = &manifold[i];
555
556 float p = vg_maxf( 0.0f, ct->p - 0.0025f ),
557 cur = vg_clampf( v3_dot( ct->n, dt ), 0.0f, p );
558 v3_muladds( dt, ct->n, (p - cur) * 0.333333333f, dt );
559 }
560 }
561 v3_add( dt, phys->rb.co, phys->rb.co );
562 return;
563 }
564 }
565
566 phys->rb.co[1] -= max_dist * 0.125f;
567
568 player_walk_update_collision();
569 len = 0;
570 len += rb_sphere_scene( rbf, &world.rb_geo, manifold+len );
571 len += rb_sphere_scene( rbb, &world.rb_geo, manifold+len );
572 }
573
574 /* Transitioning into air mode */
575 phys->rb.co[1] = start_y;
576 }
577 }
578
579 VG_STATIC void player_grind(void)
580 {
581 struct player_phys *phys = &player.phys;
582
583 v3f closest;
584 int idx = bh_closest_point( world.grind_bh, phys->rb.co, closest, INFINITY );
585 if( idx == -1 )
586 return;
587
588 struct grind_edge *edge = &world.grind_edges[ idx ];
589
590 vg_line( phys->rb.co, closest, 0xff000000 );
591 vg_line_cross( closest, 0xff000000, 0.3f );
592 vg_line( edge->p0, edge->p1, 0xff000000 );
593
594 v3f grind_delta;
595 v3_sub( closest, phys->rb.co, grind_delta );
596
597 float p = v3_dot( phys->rb.forward, grind_delta );
598 v3_muladds( grind_delta, phys->rb.forward, -p, grind_delta );
599
600 float a = vg_maxf( 0.0f, 4.0f-v3_dist2( closest, phys->rb.co ) );
601 v3_muladds( phys->rb.v, grind_delta, a*0.2f, phys->rb.v );
602 }
603
604 VG_STATIC int player_update_grind_collision( rb_ct *contact )
605 {
606 struct player_phys *phys = &player.phys;
607
608 bh_iter it;
609 bh_iter_init( 0, &it );
610
611 boxf region;
612
613 v3f p0, p1, c0, c1;
614 v3_muladds( phys->rb.co, phys->rb.forward, 0.5f, p0 );
615 v3_muladds( phys->rb.co, phys->rb.forward, -0.5f, p1 );
616 v3_muladds( p0, phys->rb.up, 0.125f, p0 );
617 v3_muladds( p1, phys->rb.up, 0.125f, p1 );
618
619 box_init_inf( region );
620 box_addpt( region, p0 );
621 box_addpt( region, p1 );
622
623 float const k_r = 0.25f;
624 v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] );
625 v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] );
626
627 vg_line( p0, p1, 0xff0000ff );
628 vg_line_boxf( region, 0xff0000ff );
629
630 float closest = k_r*k_r;
631 struct grind_edge *closest_edge = NULL;
632
633 int idx;
634 while( bh_next( world.grind_bh, &it, region, &idx ) )
635 {
636 struct grind_edge *edge = &world.grind_edges[ idx ];
637
638 float s,t;
639 v3f pa, pb;
640
641 float d2 =
642 closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb );
643
644 if( d2 < closest )
645 {
646 closest = d2;
647 closest_edge = edge;
648 v3_copy( pa, c0 );
649 v3_copy( pb, c1 );
650 }
651 }
652
653 if( closest_edge )
654 {
655 vg_line_cross( c0, 0xff000000, 0.1f );
656 vg_line_cross( c1, 0xff000000, 0.1f );
657 vg_line( c0, c1, 0xff000000 );
658
659 v3f delta;
660 v3_sub( c1, c0, delta );
661
662 if( v3_dot( delta, phys->rb.up ) > 0.0f )
663 {
664 v3_copy( delta, contact->n );
665 float l = v3_length( contact->n );
666 v3_muls( contact->n, 1.0f/l, contact->n );
667 contact->p = l;
668 contact->type = k_contact_type_edge;
669 contact->element_id = 0;
670 v3_copy( c1, contact->co );
671 contact->rba = &player.phys.rb;
672 contact->rbb = &world.rb_geo;
673
674 v3f edge_dir, axis_dir;
675 v3_sub( closest_edge->p1, closest_edge->p0, edge_dir );
676 v3_normalize( edge_dir );
677 v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
678 v3_cross( edge_dir, axis_dir, contact->n );
679
680 return 1;
681 }
682 else
683 return -1;
684 }
685
686 return 0;
687
688 #if 0
689 v3f closest;
690 int idx = bh_closest_point( world.grind_bh, phys->rb.co, closest, INFINITY );
691 if( idx != -1 )
692 {
693 struct grind_edge *edge = &world.grind_edges[ idx ];
694
695 vg_line( phys->rb.co, closest, 0xff000000 );
696 vg_line_cross( closest, 0xff000000, 0.3f );
697 vg_line( edge->p0, edge->p1, 0xff000000 );
698
699 v3f delta;
700 v3_sub( closest, phys->rb.co, delta );
701
702 if( v3_length2(delta) < 0.5f*0.5f )
703 {
704 v3_copy( closest, phys->rb.co );
705
706 v3f line_dir;
707 v3_sub( edge->p1, edge->p0, line_dir );
708 v3_normalize( line_dir );
709
710 float p = v3_dot( phys->rb.v, line_dir );
711 v3_muls( line_dir, p, phys->rb.v );
712 phys->grind = 1;
713 }
714 else
715 phys->grind = 0;
716 }
717 #endif
718 }
719
720 /* Manifold must be able to hold at least 64 elements */
721 VG_STATIC int player_update_collision_manifold( rb_ct *manifold )
722 {
723 struct player_phys *phys = &player.phys;
724
725 rigidbody *rbf = &player.collide_front,
726 *rbb = &player.collide_back;
727
728 m3x3_copy( phys->rb.to_world, player.collide_front.to_world );
729 m3x3_copy( phys->rb.to_world, player.collide_back.to_world );
730
731 player.air_blend = vg_lerpf( player.air_blend, phys->in_air, 0.1f );
732 float h = player.air_blend*0.2f;
733
734 m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h,-k_board_length}, rbf->co );
735 v3_copy( rbf->co, rbf->to_world[3] );
736 m4x3_mulv( phys->rb.to_world, (v3f){0.0f,h, k_board_length}, rbb->co );
737 v3_copy( rbb->co, rbb->to_world[3] );
738
739 m4x3_invert_affine( rbf->to_world, rbf->to_local );
740 m4x3_invert_affine( rbb->to_world, rbb->to_local );
741
742 rb_update_bounds( rbf );
743 rb_update_bounds( rbb );
744
745 rb_debug( rbf, 0xff00ffff );
746 rb_debug( rbb, 0xffffff00 );
747
748 int len_f = 0,
749 len_b = 0;
750
751 len_f = rb_sphere_scene( rbf, &world.rb_geo, manifold );
752 rb_manifold_filter_coplanar( manifold, len_f, 0.05f );
753 if( len_f > 1 )
754 {
755 rb_manifold_filter_backface( manifold, len_f );
756 rb_manifold_filter_joint_edges( manifold, len_f, 0.05f );
757 rb_manifold_filter_pairs( manifold, len_f, 0.05f );
758 }
759 len_f = rb_manifold_apply_filtered( manifold, len_f );
760
761 rb_ct *man_b = &manifold[len_f];
762 len_b = rb_sphere_scene( rbb, &world.rb_geo, man_b );
763 rb_manifold_filter_coplanar( man_b, len_b, 0.05f );
764 if( len_b > 1 )
765 {
766 rb_manifold_filter_backface( man_b, len_b );
767 rb_manifold_filter_joint_edges( man_b, len_b, 0.05f );
768 rb_manifold_filter_pairs( man_b, len_b, 0.05f );
769 }
770 len_b = rb_manifold_apply_filtered( man_b, len_b );
771
772 #if 0
773 /*
774 * Preprocess collision points, and create a surface picture.
775 * we want contacts that are within our 'capsule's internal line to be
776 * clamped so that they face the line and do not oppose, to stop the
777 * player hanging up on stuff
778 */
779 for( int i=0; i<len; i++ )
780 {
781 v3f dfront, dback;
782 v3_sub( manifold[i].co, rbf->co, dfront );
783 v3_sub( manifold[i].co, rbb->co, dback );
784
785 if( (v3_dot( dfront, phys->rb.forward ) < -0.02f) &&
786 (v3_dot( dback, phys->rb.forward ) > 0.02f))
787 {
788 float p = v3_dot( manifold[i].n, phys->rb.forward );
789 v3_muladds( manifold[i].n, phys->rb.forward, -p, manifold[i].n );
790 v3_normalize( manifold[i].n );
791 }
792 }
793 #endif
794
795 return len_f + len_b;
796 }
797
798 VG_STATIC void player_adhere_ground( rb_ct *manifold, int len )
799 {
800 struct player_phys *phys = &player.phys;
801 int was_in_air = phys->in_air;
802
803 v3f surface_avg;
804 v3_zero( surface_avg );
805
806 if( len == 0 )
807 {
808 phys->lift_frames ++;
809
810 if( phys->lift_frames >= 8 )
811 phys->in_air = 1;
812 }
813 else
814 {
815 for( int i=0; i<len; i++ )
816 v3_add( surface_avg, manifold[i].n, surface_avg );
817 v3_normalize( surface_avg );
818
819 if( v3_dot( phys->rb.v, surface_avg ) > 0.7f )
820 {
821 phys->lift_frames ++;
822
823 if( phys->lift_frames >= 8 )
824 phys->in_air = 1;
825 }
826 else
827 {
828 phys->in_air = 0;
829 phys->lift_frames = 0;
830 v3f projected, axis;
831
832 float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
833 v3_muladds( phys->rb.v, phys->rb.up, DOWNFORCE, phys->rb.v );
834
835 float d = v3_dot( phys->rb.forward, surface_avg );
836 v3_muladds( surface_avg, phys->rb.forward, -d, projected );
837 v3_normalize( projected );
838
839 float angle = v3_dot( phys->rb.up, projected );
840 v3_cross( phys->rb.up, projected, axis );
841
842 v3f p0, p1;
843 v3_add( phys->rb.co, projected, p0 );
844 v3_add( phys->rb.co, phys->rb.up, p1 );
845 vg_line( phys->rb.co, p0, 0xff00ff00 );
846 vg_line( phys->rb.co, p1, 0xff000fff );
847
848 if( fabsf(angle) < 0.999f )
849 {
850 v4f correction;
851 q_axis_angle( correction, axis,
852 acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
853 q_mul( correction, phys->rb.q, phys->rb.q );
854 }
855 }
856 }
857
858 if( !was_in_air && phys->in_air )
859 player_start_air();
860 }
861
862 VG_STATIC void player_collision_response( rb_ct *manifold, int len )
863 {
864 struct player_phys *phys = &player.phys;
865
866 for( int j=0; j<5; j++ )
867 {
868 for( int i=0; i<len; i++ )
869 {
870 struct contact *ct = &manifold[i];
871
872 v3f dv, delta;
873 v3_sub( ct->co, phys->rb.co, delta );
874 v3_cross( phys->rb.w, delta, dv );
875 v3_add( phys->rb.v, dv, dv );
876
877 float vn = -v3_dot( dv, ct->n );
878 vn += ct->bias;
879
880 float temp = ct->norm_impulse;
881 ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
882 vn = ct->norm_impulse - temp;
883
884 v3f impulse;
885 v3_muls( ct->n, vn, impulse );
886
887 if( fabsf(v3_dot( impulse, phys->rb.forward )) > 10.0f ||
888 fabsf(v3_dot( impulse, phys->rb.up )) > 50.0f )
889 {
890 player_kill();
891 return;
892 }
893
894 v3_add( impulse, phys->rb.v, phys->rb.v );
895 v3_cross( delta, impulse, impulse );
896
897 /*
898 * W Impulses are limited to the Y and X axises, we don't really want
899 * roll angular velocities being included.
900 *
901 * Can also tweak the resistance of each axis here by scaling the wx,wy
902 * components.
903 */
904
905 float wy = v3_dot( phys->rb.up, impulse ),
906 wx = v3_dot( phys->rb.right, impulse )*1.8f;
907
908 v3_muladds( phys->rb.w, phys->rb.up, wy, phys->rb.w );
909 v3_muladds( phys->rb.w, phys->rb.right, wx, phys->rb.w );
910 }
911 }
912 }
913
914 VG_STATIC void player_save_frame(void)
915 {
916 player.phys_gate_frame = player.phys;
917 }
918
919 VG_STATIC void player_restore_frame(void)
920 {
921 player.phys = player.phys_gate_frame;
922 rb_update_transform( &player.phys.rb );
923 }
924
925 VG_STATIC void player_integrate(void)
926 {
927 struct player_phys *phys = &player.phys;
928 apply_gravity( phys->rb.v, VG_TIMESTEP_FIXED );
929 v3_muladds( phys->rb.co, phys->rb.v, VG_TIMESTEP_FIXED, phys->rb.co );
930 }
931
932 VG_STATIC void player_do_motion(void)
933 {
934 struct player_phys *phys = &player.phys;
935
936 if( world.water.enabled )
937 {
938 if( (phys->rb.co[1] < 0.0f) && !player.is_dead )
939 {
940 audio_lock();
941 audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
942 audio_player_set_position( &audio_player_extra, phys->rb.co );
943 audio_player_set_vol( &audio_player_extra, 20.0f );
944 audio_player_playclip( &audio_player_extra, &audio_splash );
945 audio_unlock();
946
947 player_kill();
948 }
949 }
950
951 v3f prevco;
952 v3_copy( phys->rb.co, prevco );
953
954 if( phys->on_board )
955 {
956 rb_ct manifold[72];
957 int len = player_update_collision_manifold( manifold );
958 int grind_col = player_update_grind_collision( &manifold[len] );
959
960 if( grind_col )
961 {
962 phys->grind = 1;
963 v3f up = { 0.0f, 1.0f, 0.0f };
964 float angle = v3_dot( phys->rb.up, up );
965 v3f axis;
966 v3_cross( phys->rb.up, up, axis );
967
968 if( fabsf(angle) < 0.99f )
969 {
970 v4f correction;
971 q_axis_angle( correction, axis,
972 VG_TIMESTEP_FIXED * 10.0f * acosf(angle) );
973 q_mul( correction, phys->rb.q, phys->rb.q );
974 }
975
976 float const DOWNFORCE = -k_downforce*2.0f*VG_TIMESTEP_FIXED;
977 v3_muladds( phys->rb.v, phys->rb.up, DOWNFORCE, phys->rb.v );
978 }
979 else
980 {
981 phys->grind = 0;
982 player_adhere_ground( manifold, len );
983 }
984
985 rb_presolve_contacts( manifold, len+ VG_MAX(0,grind_col) );
986 player_collision_response( manifold, len+ VG_MAX(0,grind_col) );
987
988 player_physics_control_passive();
989
990 if( grind_col )
991 player_physics_control_grind();
992 else
993 {
994 if( phys->in_air )
995 player_physics_control_air();
996 else
997 player_physics_control();
998 }
999
1000 player_integrate();
1001 }
1002 else
1003 player_walk_physics();
1004
1005
1006 /* Real angular velocity integration */
1007 v3_lerp( phys->rb.w, (v3f){0.0f,0.0f,0.0f}, 0.125f, phys->rb.w );
1008 if( v3_length2( phys->rb.w ) > 0.0f )
1009 {
1010 v4f rotation;
1011 v3f axis;
1012 v3_copy( phys->rb.w, axis );
1013
1014 float mag = v3_length( axis );
1015 v3_divs( axis, mag, axis );
1016 q_axis_angle( rotation, axis, mag*k_rb_delta );
1017 q_mul( rotation, phys->rb.q, phys->rb.q );
1018 }
1019
1020 /* Faux angular velocity */
1021 v4f rotate;
1022
1023 float lerpq = phys->in_air? 0.04f: 0.3f;
1024 phys->siY = vg_lerpf( phys->siY, phys->iY, lerpq );
1025
1026 q_axis_angle( rotate, phys->rb.up, phys->siY );
1027 q_mul( rotate, phys->rb.q, phys->rb.q );
1028 phys->iY = 0.0f;
1029
1030 /*
1031 * Gate intersection, by tracing a line over the gate planes
1032 */
1033 for( int i=0; i<world.gate_count; i++ )
1034 {
1035 struct route_gate *rg = &world.gates[i];
1036 teleport_gate *gate = &rg->gate;
1037
1038 if( gate_intersect( gate, phys->rb.co, prevco ) )
1039 {
1040 m4x3_mulv( gate->transport, phys->rb.co, phys->rb.co );
1041 m3x3_mulv( gate->transport, phys->rb.v, phys->rb.v );
1042 m3x3_mulv( gate->transport, phys->vl, phys->vl );
1043 m3x3_mulv( gate->transport, phys->v_last, phys->v_last );
1044 m3x3_mulv( gate->transport, phys->m, phys->m );
1045 m3x3_mulv( gate->transport, phys->bob, phys->bob );
1046
1047 v4f transport_rotation;
1048 m3x3_q( gate->transport, transport_rotation );
1049 q_mul( transport_rotation, phys->rb.q, phys->rb.q );
1050
1051 world_routes_activate_gate( i );
1052
1053 if( !phys->on_board )
1054 {
1055 v3f fwd_dir = {cosf(player.angles[0]),
1056 0.0f,
1057 sinf(player.angles[0])};
1058 m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
1059
1060 player.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
1061 }
1062
1063 player.rewind_length = 0;
1064 player.rewind_total_length = 0.0f;
1065 player.rewind_incrementer = 10000;
1066 player_save_frame();
1067
1068 audio_lock();
1069 audio_play_oneshot( &audio_gate_pass, 1.0f );
1070 audio_unlock();
1071 break;
1072 }
1073 }
1074
1075 rb_update_transform( &phys->rb );
1076 }
1077
1078 VG_STATIC void player_freecam(void)
1079 {
1080 player_mouseview();
1081
1082 float movespeed = fc_speed;
1083 v3f lookdir = { 0.0f, 0.0f, -1.0f },
1084 sidedir = { 1.0f, 0.0f, 0.0f };
1085
1086 m3x3_mulv( camera_mtx, lookdir, lookdir );
1087 m3x3_mulv( camera_mtx, sidedir, sidedir );
1088
1089 static v3f move_vel = { 0.0f, 0.0f, 0.0f };
1090
1091 /* TODO */
1092 #if 0
1093 if( vg_get_button( "forward" ) )
1094 v3_muladds( move_vel, lookdir, VG_TIMESTEP_FIXED * movespeed, move_vel );
1095 if( vg_get_button( "back" ) )
1096 v3_muladds( move_vel, lookdir, VG_TIMESTEP_FIXED *-movespeed, move_vel );
1097 if( vg_get_button( "left" ) )
1098 v3_muladds( move_vel, sidedir, VG_TIMESTEP_FIXED *-movespeed, move_vel );
1099 if( vg_get_button( "right" ) )
1100 v3_muladds( move_vel, sidedir, VG_TIMESTEP_FIXED * movespeed, move_vel );
1101 #endif
1102
1103 v3_muls( move_vel, 0.7f, move_vel );
1104 v3_add( move_vel, player.camera_pos, player.camera_pos );
1105 }
1106
1107 VG_STATIC int reset_player( int argc, char const *argv[] )
1108 {
1109 struct player_phys *phys = &player.phys;
1110 struct respawn_point *rp = NULL, *r;
1111
1112 if( argc == 1 )
1113 {
1114 for( int i=0; i<world.spawn_count; i++ )
1115 {
1116 r = &world.spawns[i];
1117 if( !strcmp( r->name, argv[0] ) )
1118 {
1119 rp = r;
1120 break;
1121 }
1122 }
1123
1124 if( !rp )
1125 vg_warn( "No spawn named '%s'\n", argv[0] );
1126 }
1127
1128 if( !rp )
1129 {
1130 float min_dist = INFINITY;
1131
1132 for( int i=0; i<world.spawn_count; i++ )
1133 {
1134 r = &world.spawns[i];
1135 float d = v3_dist2( r->co, phys->rb.co );
1136
1137 vg_info( "Dist %s : %f\n", r->name, d );
1138 if( d < min_dist )
1139 {
1140 min_dist = d;
1141 rp = r;
1142 }
1143 }
1144 }
1145
1146 if( !rp )
1147 {
1148 vg_error( "No spawn found\n" );
1149 if( !world.spawn_count )
1150 return 0;
1151
1152 rp = &world.spawns[0];
1153 }
1154
1155 player.is_dead = 0;
1156
1157 m3x3f the_long_way;
1158 q_m3x3( rp->q, the_long_way );
1159
1160 v3f delta = {1.0f,0.0f,0.0f};
1161 m3x3_mulv( the_long_way, delta, delta );
1162
1163 player.angles[0] = atan2f( delta[0], -delta[2] );
1164 player.angles[1] = -asinf( delta[1] );
1165
1166
1167 v4_copy( rp->q, phys->rb.q );
1168 v3_copy( rp->co, phys->rb.co );
1169 v3_zero( phys->rb.v );
1170
1171 phys->vswitch = 1.0f;
1172 phys->slip_last = 0.0f;
1173 phys->in_air = 1;
1174 phys->on_board = 0;
1175 m3x3_identity( phys->vr );
1176
1177 player.mdl.shoes[0] = 1;
1178 player.mdl.shoes[1] = 1;
1179
1180 rb_update_transform( &phys->rb );
1181 player_save_frame();
1182 return 1;
1183 }
1184
1185 #endif /* PLAYER_PHYSICS_H */