LPR - Skating
[carveJwlIkooP6JGAAIwe30JlM.git] / player_device_skate.h
1 #ifndef PLAYER_DEVICE_SKATE_H
2 #define PLAYER_DEVICE_SKATE_H
3
4 #include "player_interface.h"
5 #include "skeleton.h"
6 #include "player_model.h"
7
8 struct player_device_skate
9 {
10 struct
11 {
12 enum skate_activity
13 {
14 k_skate_activity_air,
15 k_skate_activity_ground,
16 k_skate_activity_grind
17 }
18 activity,
19 activity_prev;
20
21 float steery,
22 steerx,
23 steery_s,
24 steerx_s,
25 reverse,
26 slip;
27
28 m3x3f velocity_bias,
29 velocity_bias_pstep;
30
31 int lift_frames;
32
33 v3f throw_v;
34 v3f cog_v, cog;
35
36 float grabbing;
37 v2f grab_mouse_delta;
38
39 int charging_jump, jump_dir;
40 float jump_charge;
41 double jump_time;
42
43 double start_push,
44 cur_push;
45
46 v3f vl;
47 }
48 state,
49 state_gate_storage;
50
51 struct land_prediction
52 {
53 v3f log[50];
54 v3f n;
55 u32 log_length;
56 float score;
57
58 enum prediction_type
59 {
60 k_prediction_none,
61 k_prediction_land,
62 k_prediction_grind
63 }
64 type;
65
66 u32 colour;
67 }
68 predictions[22];
69 u32 prediction_count;
70
71 /* animation */
72 struct skeleton_anim *anim_stand, *anim_highg, *anim_slide,
73 *anim_air,
74 *anim_push, *anim_push_reverse,
75 *anim_ollie, *anim_ollie_reverse,
76 *anim_grabs, *anim_stop;
77 rb_sphere sphere_front, sphere_back;
78 v3f board_offset;
79 v4f board_rotation;
80
81 float blend_slide,
82 blend_z,
83 blend_x,
84 blend_fly,
85 blend_stand,
86 blend_push,
87 blend_jump,
88 blend_airdir;
89
90 v2f wobble;
91
92 float debug_normal_pressure;
93 };
94
95 VG_STATIC void player_skate_bind( player_interface *player,
96 player_attachment *at )
97 {
98 struct player_device_skate *s = at->storage;
99 struct player_avatar *av = player->playeravatar;
100 struct skeleton *sk = &av->sk;
101
102 rb_update_transform( &player->rb );
103 s->anim_stand = skeleton_get_anim( sk, "pose_stand" );
104 s->anim_highg = skeleton_get_anim( sk, "pose_highg" );
105 s->anim_air = skeleton_get_anim( sk, "pose_air" );
106 s->anim_slide = skeleton_get_anim( sk, "pose_slide" );
107 s->anim_push = skeleton_get_anim( sk, "push" );
108 s->anim_push_reverse = skeleton_get_anim( sk, "push_reverse" );
109 s->anim_ollie = skeleton_get_anim( sk, "ollie" );
110 s->anim_ollie_reverse = skeleton_get_anim( sk, "ollie_reverse" );
111 s->anim_grabs = skeleton_get_anim( sk, "grabs" );
112 }
113
114 VG_STATIC void player_skate_pre_update( player_interface *player,
115 player_attachment *at )
116 {
117 }
118
119 /*
120 * Collision detection routines
121 *
122 *
123 */
124
125 /*
126 * Does collision detection on a sphere vs world, and applies some smoothing
127 * filters to the manifold afterwards
128 */
129 VG_STATIC int skate_collide_smooth( player_interface *player,
130 m4x3f mtx, rb_sphere *sphere,
131 rb_ct *man )
132 {
133 debug_sphere( mtx, sphere->radius, VG__BLACK );
134
135 int len = 0;
136 len = rb_sphere__scene( mtx, sphere, NULL, &world.rb_geo.inf.scene, man );
137
138 for( int i=0; i<len; i++ )
139 {
140 man[i].rba = &player->rb;
141 man[i].rbb = NULL;
142 }
143
144 rb_manifold_filter_coplanar( man, len, 0.05f );
145
146 if( len > 1 )
147 {
148 rb_manifold_filter_backface( man, len );
149 rb_manifold_filter_joint_edges( man, len, 0.05f );
150 rb_manifold_filter_pairs( man, len, 0.05f );
151 }
152 int new_len = rb_manifold_apply_filtered( man, len );
153 if( len && !new_len )
154 len = 1;
155 else
156 len = new_len;
157
158 return len;
159 }
160 /*
161 * Gets the closest grindable edge to the player within max_dist
162 */
163 VG_STATIC struct grind_edge *skate_collect_grind_edge( v3f p0, v3f p1,
164 v3f c0, v3f c1,
165 float max_dist )
166 {
167 bh_iter it;
168 bh_iter_init( 0, &it );
169
170 boxf region;
171
172 box_init_inf( region );
173 box_addpt( region, p0 );
174 box_addpt( region, p1 );
175
176 float k_r = max_dist;
177 v3_add( (v3f){ k_r, k_r, k_r}, region[1], region[1] );
178 v3_add( (v3f){-k_r,-k_r,-k_r}, region[0], region[0] );
179
180 float closest = k_r*k_r;
181 struct grind_edge *closest_edge = NULL;
182
183 int idx;
184 while( bh_next( world.grind_bh, &it, region, &idx ) )
185 {
186 struct grind_edge *edge = &world.grind_edges[ idx ];
187
188 float s,t;
189 v3f pa, pb;
190
191 float d2 =
192 closest_segment_segment( p0, p1, edge->p0, edge->p1, &s,&t, pa, pb );
193
194 if( d2 < closest )
195 {
196 closest = d2;
197 closest_edge = edge;
198 v3_copy( pa, c0 );
199 v3_copy( pb, c1 );
200 }
201 }
202
203 return closest_edge;
204 }
205
206 VG_STATIC int skate_grind_collide( player_interface *player,
207 player_attachment *at, rb_ct *contact )
208 {
209 v3f p0, p1, c0, c1;
210 v3_muladds( player->rb.co, player->rb.to_world[2], 0.5f, p0 );
211 v3_muladds( player->rb.co, player->rb.to_world[2], -0.5f, p1 );
212 v3_muladds( p0, player->rb.to_world[1], 0.125f-0.15f, p0 );
213 v3_muladds( p1, player->rb.to_world[1], 0.125f-0.15f, p1 );
214
215 float const k_r = 0.25f;
216 struct grind_edge *closest_edge = skate_collect_grind_edge( p0, p1,
217 c0, c1, k_r );
218
219 if( closest_edge )
220 {
221 v3f delta;
222 v3_sub( c1, c0, delta );
223
224 if( v3_dot( delta, player->rb.to_world[1] ) > 0.0001f )
225 {
226 contact->p = v3_length( delta );
227 contact->type = k_contact_type_edge;
228 contact->element_id = 0;
229 v3_copy( c1, contact->co );
230 contact->rba = NULL;
231 contact->rbb = NULL;
232
233 v3f edge_dir, axis_dir;
234 v3_sub( closest_edge->p1, closest_edge->p0, edge_dir );
235 v3_normalize( edge_dir );
236 v3_cross( (v3f){0.0f,1.0f,0.0f}, edge_dir, axis_dir );
237 v3_cross( edge_dir, axis_dir, contact->n );
238
239 return 1;
240 }
241 else
242 return 0;
243 }
244
245 return 0;
246 }
247
248 /*
249 *
250 * Prediction system
251 *
252 *
253 */
254
255 /*
256 * Trace a path given a velocity rotation.
257 *
258 * TODO: this MIGHT be worth doing RK4 on the gravity field.
259 */
260 VG_STATIC void skate_score_biased_path( v3f co, v3f v, m3x3f vr,
261 struct land_prediction *prediction )
262 {
263 float pstep = VG_TIMESTEP_FIXED * 10.0f;
264 float k_bias = 0.96f;
265
266 v3f pco, pco1, pv;
267 v3_copy( co, pco );
268 v3_muls( v, k_bias, pv );
269
270 m3x3_mulv( vr, pv, pv );
271 v3_muladds( pco, pv, pstep, pco );
272
273 struct grind_edge *best_grind = NULL;
274 float closest_grind = INFINITY;
275
276 float grind_score = INFINITY,
277 air_score = INFINITY;
278
279 prediction->log_length = 0;
280
281 for( int i=0; i<vg_list_size(prediction->log); i++ )
282 {
283 v3_copy( pco, pco1 );
284
285 pv[1] += -k_gravity * pstep;
286
287 m3x3_mulv( vr, pv, pv );
288 v3_muladds( pco, pv, pstep, pco );
289
290 v3f vdir;
291
292 v3_sub( pco, pco1, vdir );
293
294 float l = v3_length( vdir );
295 v3_muls( vdir, 1.0f/l, vdir );
296
297 v3f c0, c1;
298 struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
299 c0, c1, 0.4f );
300
301 if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
302 {
303 float d2 = v3_dist2( c0, c1 );
304 if( d2 < closest_grind )
305 {
306 closest_grind = d2;
307 best_grind = ge;
308 grind_score = closest_grind * 0.05f;
309 }
310 }
311
312 v3f n1;
313
314 float t1;
315 int idx = spherecast_world( pco1, pco, 0.4f, &t1, n1 );
316 if( idx != -1 )
317 {
318 v3_copy( n1, prediction->n );
319 air_score = -v3_dot( pv, n1 );
320
321 u32 vert_index = world.scene_geo->arrindices[ idx*3 ];
322 struct world_material *mat = world_tri_index_material( vert_index );
323
324 /* Bias prediction towords ramps */
325 if( mat->info.flags & k_material_flag_skate_surface )
326 air_score *= 0.1f;
327
328 v3_lerp( pco1, pco, t1, prediction->log[ prediction->log_length ++ ] );
329 break;
330 }
331
332 v3_copy( pco, prediction->log[ prediction->log_length ++ ] );
333 }
334
335 if( grind_score < air_score )
336 {
337 prediction->score = grind_score;
338 prediction->type = k_prediction_grind;
339 }
340 else if( air_score < INFINITY )
341 {
342 prediction->score = air_score;
343 prediction->type = k_prediction_land;
344 }
345 else
346 {
347 prediction->score = INFINITY;
348 prediction->type = k_prediction_none;
349 }
350 }
351
352 VG_STATIC
353 void player_approximate_best_trajectory( player_interface *player,
354 struct player_device_skate *s )
355 {
356 float pstep = VG_TIMESTEP_FIXED * 10.0f;
357 float best_velocity_delta = -9999.9f;
358
359 v3f axis;
360 v3_cross( player->rb.to_world[1], player->rb.v, axis );
361 v3_normalize( axis );
362
363 s->prediction_count = 0;
364 m3x3_identity( s->state.velocity_bias );
365
366 float best_vmod = 0.0f,
367 min_score = INFINITY,
368 max_score = -INFINITY;
369
370 /*
371 * Search a broad selection of futures
372 */
373 for( int m=-3;m<=12; m++ )
374 {
375 struct land_prediction *p = &s->predictions[ s->prediction_count ++ ];
376
377 float vmod = ((float)m / 15.0f)*0.09f;
378
379 m3x3f bias;
380 v4f bias_q;
381
382 q_axis_angle( bias_q, axis, vmod );
383 q_m3x3( bias_q, bias );
384
385 skate_score_biased_path( player->rb.co, player->rb.v, bias, p );
386
387 if( p->type != k_prediction_none )
388 {
389 if( p->score < min_score )
390 {
391 min_score = p->score;
392 best_vmod = vmod;
393 }
394
395 if( p->score > max_score )
396 max_score = p->score;
397 }
398 }
399
400 v4f vr_q;
401 q_axis_angle( vr_q, axis, best_vmod*0.1f );
402 q_m3x3( vr_q, s->state.velocity_bias );
403
404 q_axis_angle( vr_q, axis, best_vmod );
405 q_m3x3( vr_q, s->state.velocity_bias_pstep );
406
407 /*
408 * Logging
409 */
410 for( int i=0; i<s->prediction_count; i ++ )
411 {
412 struct land_prediction *p = &s->predictions[i];
413
414 float l = p->score;
415
416 if( l < 0.0f )
417 {
418 vg_error( "negative score! (%f)\n", l );
419 }
420
421 l -= min_score;
422 l /= (max_score-min_score);
423 l = 1.0f - l;
424 l *= 255.0f;
425
426 p->colour = l;
427 p->colour <<= 8;
428 p->colour |= 0xff000000;
429 }
430 }
431
432 /*
433 *
434 * Varius physics models
435 * ------------------------------------------------
436 */
437
438 VG_STATIC void skate_apply_grind_model( player_interface *player,
439 struct player_device_skate *s,
440 rb_ct *manifold, int len )
441 {
442 /* FIXME: Queue audio events instead */
443 if( len == 0 )
444 {
445 if( s->state.activity == k_skate_activity_grind )
446 {
447 #if 0
448 audio_lock();
449 audio_player_set_flags( &audio_player_extra,
450 AUDIO_FLAG_SPACIAL_3D );
451 audio_player_set_position( &audio_player_extra, player.rb.co );
452 audio_player_set_vol( &audio_player_extra, 20.0f );
453 audio_player_playclip( &audio_player_extra, &audio_board[6] );
454 audio_unlock();
455 #endif
456
457 s->state.activity = k_skate_activity_air;
458 }
459 return;
460 }
461
462 v2f steer = { player->input_js1h->axis.value,
463 player->input_js1v->axis.value };
464 v2_normalize_clamp( steer );
465
466 s->state.steery -= steer[0] * k_steer_air * k_rb_delta;
467 s->state.steerx += steer[1] * s->state.reverse * k_steer_air * k_rb_delta;
468
469 #if 0
470 v4f rotate;
471 q_axis_angle( rotate, player->rb.to_world[0], siX );
472 q_mul( rotate, player.rb.q, player.rb.q );
473 #endif
474
475 s->state.slip = 0.0f;
476 s->state.activity = k_skate_activity_grind;
477
478 /* TODO: Compression */
479 v3f up = { 0.0f, 1.0f, 0.0f };
480 float angle = v3_dot( player->rb.to_world[1], up );
481
482 if( fabsf(angle) < 0.99f )
483 {
484 v3f axis;
485 v3_cross( player->rb.to_world[1], up, axis );
486
487 v4f correction;
488 q_axis_angle( correction, axis, k_rb_delta * 10.0f * acosf(angle) );
489 q_mul( correction, player->rb.q, player->rb.q );
490 }
491
492 float const DOWNFORCE = -k_downforce*1.2f*VG_TIMESTEP_FIXED;
493 v3_muladds( player->rb.v, manifold->n, DOWNFORCE, player->rb.v );
494 m3x3_identity( s->state.velocity_bias );
495 m3x3_identity( s->state.velocity_bias_pstep );
496
497 if( s->state.activity_prev != k_skate_activity_grind )
498 {
499 /* FIXME: Queue audio events instead */
500 #if 0
501 audio_lock();
502 audio_player_set_flags( &audio_player_extra,
503 AUDIO_FLAG_SPACIAL_3D );
504 audio_player_set_position( &audio_player_extra, player.rb.co );
505 audio_player_set_vol( &audio_player_extra, 20.0f );
506 audio_player_playclip( &audio_player_extra, &audio_board[5] );
507 audio_unlock();
508 #endif
509 }
510 }
511
512 /*
513 * Air control, no real physics
514 */
515 VG_STATIC void skate_apply_air_model( player_interface *player,
516 struct player_device_skate *s )
517 {
518 if( s->state.activity != k_skate_activity_air )
519 return;
520
521 if( s->state.activity_prev != k_skate_activity_air )
522 player_approximate_best_trajectory( player, s );
523
524 m3x3_mulv( s->state.velocity_bias, player->rb.v, player->rb.v );
525 ray_hit hit;
526
527 /*
528 * Prediction
529 */
530 float pstep = VG_TIMESTEP_FIXED * 1.0f;
531 float k_bias = 0.98f;
532
533 v3f pco, pco1, pv;
534 v3_copy( player->rb.co, pco );
535 v3_muls( player->rb.v, 1.0f, pv );
536
537 float time_to_impact = 0.0f;
538 float limiter = 1.0f;
539
540 struct grind_edge *best_grind = NULL;
541 float closest_grind = INFINITY;
542
543 v3f target_normal = { 0.0f, 1.0f, 0.0f };
544 int has_target = 0;
545
546 for( int i=0; i<250; i++ )
547 {
548 v3_copy( pco, pco1 );
549 m3x3_mulv( s->state.velocity_bias, pv, pv );
550
551 pv[1] += -k_gravity * pstep;
552 v3_muladds( pco, pv, pstep, pco );
553
554 ray_hit contact;
555 v3f vdir;
556
557 v3_sub( pco, pco1, vdir );
558 contact.dist = v3_length( vdir );
559 v3_divs( vdir, contact.dist, vdir);
560
561 v3f c0, c1;
562 struct grind_edge *ge = skate_collect_grind_edge( pco, pco1,
563 c0, c1, 0.4f );
564
565 if( ge && (v3_dot((v3f){0.0f,1.0f,0.0f},vdir) < -0.2f ) )
566 {
567 vg_line( ge->p0, ge->p1, 0xff0000ff );
568 vg_line_cross( pco, 0xff0000ff, 0.25f );
569 has_target = 1;
570 break;
571 }
572
573 float orig_dist = contact.dist;
574 if( ray_world( pco1, vdir, &contact ) )
575 {
576 v3_copy( contact.normal, target_normal );
577 has_target = 1;
578 time_to_impact += (contact.dist/orig_dist)*pstep;
579 vg_line_cross( contact.pos, 0xffff0000, 0.25f );
580 break;
581 }
582 time_to_impact += pstep;
583 }
584
585 if( has_target )
586 {
587 float angle = v3_dot( player->rb.to_world[1], target_normal );
588 v3f axis;
589 v3_cross( player->rb.to_world[1], target_normal, axis );
590
591 limiter = vg_minf( 5.0f, time_to_impact )/5.0f;
592 limiter = 1.0f-limiter;
593 limiter *= limiter;
594 limiter = 1.0f-limiter;
595
596 if( fabsf(angle) < 0.99f )
597 {
598 v4f correction;
599 q_axis_angle( correction, axis,
600 acosf(angle)*(1.0f-limiter)*3.0f*VG_TIMESTEP_FIXED );
601 q_mul( correction, player->rb.q, player->rb.q );
602 }
603 }
604
605 v2f steer = { player->input_js1h->axis.value,
606 player->input_js1v->axis.value };
607 v2_normalize_clamp( steer );
608
609 s->state.steery -= steer[0] * k_steer_air * VG_TIMESTEP_FIXED;
610 s->state.steerx += steer[1] * s->state.reverse * k_steer_air
611 * limiter * k_rb_delta;
612 }
613
614 VG_STATIC void skate_get_board_points( player_interface *player,
615 struct player_device_skate *s,
616 v3f front, v3f back )
617 {
618 v3f pos_front = {0.0f,0.0f,-k_board_length},
619 pos_back = {0.0f,0.0f, k_board_length};
620
621 m4x3_mulv( player->rb.to_world, pos_front, front );
622 m4x3_mulv( player->rb.to_world, pos_back, back );
623 }
624
625 /*
626 * Casts and pushes a sphere-spring model into the world
627 */
628 VG_STATIC int skate_simulate_spring( player_interface *player,
629 struct player_device_skate *s,
630 v3f pos )
631 {
632 float mod = 0.7f * player->input_grab->axis.value + 0.3f,
633 spring_k = mod * k_spring_force,
634 damp_k = mod * k_spring_dampener,
635 disp_k = 0.4f;
636
637 v3f start, end;
638 v3_copy( pos, start );
639 v3_muladds( pos, player->rb.to_world[1], -disp_k, end );
640
641 float t;
642 v3f n;
643 int hit_info = spherecast_world( start, end, 0.2f, &t, n );
644
645 if( hit_info != -1 )
646 {
647 v3f F, delta;
648 v3_sub( start, player->rb.co, delta );
649
650 float displacement = vg_clampf( 1.0f-t, 0.0f, 1.0f ),
651 damp =
652 vg_maxf( 0.0f, v3_dot( player->rb.to_world[1], player->rb.v ) );
653
654 v3_muls( player->rb.to_world[1], displacement*spring_k*k_rb_delta -
655 damp*damp_k*k_rb_delta, F );
656
657 v3_muladds( player->rb.v, F, 1.0f, player->rb.v );
658
659 /* Angular velocity */
660 v3f wa;
661 v3_cross( delta, F, wa );
662 v3_muladds( player->rb.w, wa, k_spring_angular, player->rb.w );
663
664 v3_lerp( start, end, t, pos );
665 return 1;
666 }
667 else
668 {
669 v3_copy( end, pos );
670 return 0;
671 }
672 }
673
674
675 /*
676 * Handles connection between the player and the ground
677 */
678 VG_STATIC void skate_apply_interface_model( player_interface *player,
679 struct player_device_skate *s,
680 rb_ct *manifold, int len )
681 {
682 if( !((s->state.activity == k_skate_activity_ground) ||
683 (s->state.activity == k_skate_activity_air )) )
684 return;
685
686 if( s->state.activity == k_skate_activity_air )
687 s->debug_normal_pressure = 0.0f;
688 else
689 s->debug_normal_pressure = v3_dot( player->rb.to_world[1], player->rb.v );
690
691 /* springs */
692 v3f spring0, spring1;
693
694 skate_get_board_points( player, s, spring1, spring0 );
695 int spring_hit0 = skate_simulate_spring( player, s, spring0 ),
696 spring_hit1 = skate_simulate_spring( player, s, spring1 );
697
698 v3f animavg, animdelta;
699 v3_add( spring0, spring1, animavg );
700 v3_muls( animavg, 0.5f, animavg );
701
702 v3_sub( spring1, spring0, animdelta );
703 v3_normalize( animdelta );
704
705 m4x3_mulv( player->rb.to_local, animavg, s->board_offset );
706
707 float dx = -v3_dot( animdelta, player->rb.to_world[2] ),
708 dy = v3_dot( animdelta, player->rb.to_world[1] );
709
710 float angle = -atan2f( dy, dx );
711 q_axis_angle( s->board_rotation, (v3f){1.0f,0.0f,0.0f}, angle );
712
713 /* Surface connection */
714 if( len == 0 && !(spring_hit0 && spring_hit1) )
715 {
716 s->state.lift_frames ++;
717
718 if( s->state.lift_frames >= 8 )
719 s->state.activity = k_skate_activity_air;
720 }
721 else
722 {
723 v3f surface_avg;
724 v3_zero( surface_avg );
725
726 for( int i=0; i<len; i++ )
727 v3_add( surface_avg, manifold[i].n, surface_avg );
728 v3_normalize( surface_avg );
729
730 if( v3_dot( player->rb.v, surface_avg ) > 0.7f )
731 {
732 s->state.lift_frames ++;
733
734 if( s->state.lift_frames >= 8 )
735 s->state.activity = k_skate_activity_air;
736 }
737 else
738 {
739 s->state.activity = k_skate_activity_ground;
740 s->state.lift_frames = 0;
741 v3f projected, axis;
742
743 float const DOWNFORCE = -k_downforce*VG_TIMESTEP_FIXED;
744 v3_muladds( player->rb.v, player->rb.to_world[1],
745 DOWNFORCE, player->rb.v );
746
747 float d = v3_dot( player->rb.to_world[2], surface_avg );
748 v3_muladds( surface_avg, player->rb.to_world[2], -d, projected );
749 v3_normalize( projected );
750
751 float angle = v3_dot( player->rb.to_world[1], projected );
752 v3_cross( player->rb.to_world[1], projected, axis );
753
754 if( fabsf(angle) < 0.9999f )
755 {
756 v4f correction;
757 q_axis_angle( correction, axis,
758 acosf(angle)*4.0f*VG_TIMESTEP_FIXED );
759 q_mul( correction, player->rb.q, player->rb.q );
760 }
761 }
762 }
763 }
764
765 VG_STATIC void skate_apply_grab_model( player_interface *player,
766 struct player_device_skate *s )
767 {
768 float grabt = player->input_grab->axis.value;
769
770 if( grabt > 0.5f )
771 {
772 v2_muladds( s->state.grab_mouse_delta, vg.mouse_delta, 0.02f,
773 s->state.grab_mouse_delta );
774
775 v2_normalize_clamp( s->state.grab_mouse_delta );
776 }
777 else
778 v2_zero( s->state.grab_mouse_delta );
779
780 s->state.grabbing = vg_lerpf( s->state.grabbing, grabt, 8.4f*k_rb_delta );
781 }
782
783 /*
784 * Computes friction and surface interface model
785 */
786 VG_STATIC void skate_apply_friction_model( player_interface *player,
787 struct player_device_skate *s )
788 {
789 if( s->state.activity != k_skate_activity_ground )
790 return;
791
792 /*
793 * Computing localized friction forces for controlling the character
794 * Friction across X is significantly more than Z
795 */
796
797 v3f vel;
798 m3x3_mulv( player->rb.to_local, player->rb.v, vel );
799 float slip = 0.0f;
800
801 if( fabsf(vel[2]) > 0.01f )
802 slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
803
804 if( fabsf( slip ) > 1.2f )
805 slip = vg_signf( slip ) * 1.2f;
806
807 s->state.slip = slip;
808 s->state.reverse = -vg_signf(vel[2]);
809
810 vel[0] += vg_cfrictf( vel[0], k_friction_lat * k_rb_delta );
811 vel[2] += vg_cfrictf( vel[2], k_friction_resistance * k_rb_delta );
812
813 /* Pushing additive force */
814
815 if( !player->input_jump->button.value )
816 {
817 if( player->input_push->button.value )
818 {
819 if( (vg.time - s->state.cur_push) > 0.25 )
820 s->state.start_push = vg.time;
821
822 s->state.cur_push = vg.time;
823
824 double push_time = vg.time - s->state.start_push;
825
826 float cycle_time = push_time*k_push_cycle_rate,
827 accel = k_push_accel * (sinf(cycle_time)*0.5f+0.5f),
828 amt = accel * VG_TIMESTEP_FIXED,
829 current = v3_length( vel ),
830 new_vel = vg_minf( current + amt, k_max_push_speed ),
831 delta = new_vel - vg_minf( current, k_max_push_speed );
832
833 vel[2] += delta * -s->state.reverse;
834 }
835 }
836
837 /* Send back to velocity */
838 m3x3_mulv( player->rb.to_world, vel, player->rb.v );
839
840 /* Steering */
841 float input = player->input_js1h->axis.value,
842 grab = player->input_grab->axis.value,
843 steer = input * (1.0f-(s->state.jump_charge+grab)*0.4f),
844 steer_scaled = vg_signf(steer) * powf(steer,2.0f) * k_steer_ground;
845
846 s->state.steery -= steer_scaled * k_rb_delta;
847 }
848
849 VG_STATIC void skate_apply_jump_model( player_interface *player,
850 struct player_device_skate *s )
851 {
852 int charging_jump_prev = s->state.charging_jump;
853 s->state.charging_jump = player->input_jump->button.value;
854
855 /* Cannot charge this in air */
856 if( s->state.activity != k_skate_activity_ground )
857 s->state.charging_jump = 0;
858
859 if( s->state.charging_jump )
860 {
861 s->state.jump_charge += k_rb_delta * k_jump_charge_speed;
862
863 if( !charging_jump_prev )
864 s->state.jump_dir = s->state.reverse>0.0f? 1: 0;
865 }
866 else
867 {
868 s->state.jump_charge -= k_jump_charge_speed * VG_TIMESTEP_FIXED;
869 }
870
871 s->state.jump_charge = vg_clampf( s->state.jump_charge, 0.0f, 1.0f );
872
873 if( s->state.activity == k_skate_activity_air )
874 return;
875
876 /* player let go after charging past 0.2: trigger jump */
877 if( (!s->state.charging_jump) && (s->state.jump_charge > 0.2f) )
878 {
879 v3f jumpdir;
880
881 /* Launch more up if alignment is up else improve velocity */
882 float aup = v3_dot( (v3f){0.0f,1.0f,0.0f}, player->rb.to_world[1] ),
883 mod = 0.5f,
884 dir = mod + fabsf(aup)*(1.0f-mod);
885
886 v3_copy( player->rb.v, jumpdir );
887 v3_normalize( jumpdir );
888 v3_muls( jumpdir, 1.0f-dir, jumpdir );
889 v3_muladds( jumpdir, player->rb.to_world[1], dir, jumpdir );
890 v3_normalize( jumpdir );
891
892 float force = k_jump_force*s->state.jump_charge;
893 v3_muladds( player->rb.v, jumpdir, force, player->rb.v );
894 s->state.jump_charge = 0.0f;
895
896 s->state.jump_time = vg.time;
897
898 /* FIXME audio events */
899 #if 0
900 audio_lock();
901 audio_player_set_flags( &audio_player_extra, AUDIO_FLAG_SPACIAL_3D );
902 audio_player_set_position( &audio_player_extra, player.rb.co );
903 audio_player_set_vol( &audio_player_extra, 20.0f );
904 audio_player_playclip( &audio_player_extra, &audio_jumps[rand()%2] );
905 audio_unlock();
906 #endif
907 }
908 }
909
910 VG_STATIC void skate_apply_pump_model( player_interface *player,
911 struct player_device_skate *s )
912 {
913 /* Throw / collect routine
914 *
915 * TODO: Max speed boost
916 */
917 if( player->input_grab->axis.value > 0.5f )
918 {
919 if( s->state.activity == k_skate_activity_ground )
920 {
921 /* Throw */
922 v3_muls( player->rb.to_world[1], k_mmthrow_scale, s->state.throw_v );
923 }
924 }
925 else
926 {
927 /* Collect */
928 float doty = v3_dot( player->rb.to_world[1], s->state.throw_v );
929
930 v3f Fl, Fv;
931 v3_muladds( s->state.throw_v, player->rb.to_world[1], -doty, Fl);
932
933 if( s->state.activity == k_skate_activity_ground )
934 {
935 v3_muladds( player->rb.v, Fl, k_mmcollect_lat, player->rb.v );
936 v3_muladds( s->state.throw_v, Fl, -k_mmcollect_lat, s->state.throw_v );
937 }
938
939 v3_muls( player->rb.to_world[1], -doty, Fv );
940 v3_muladds( player->rb.v, Fv, k_mmcollect_vert, player->rb.v );
941 v3_muladds( s->state.throw_v, Fv, k_mmcollect_vert, s->state.throw_v );
942 }
943
944 /* Decay */
945 if( v3_length2( s->state.throw_v ) > 0.0001f )
946 {
947 v3f dir;
948 v3_copy( s->state.throw_v, dir );
949 v3_normalize( dir );
950
951 float max = v3_dot( dir, s->state.throw_v ),
952 amt = vg_minf( k_mmdecay * k_rb_delta, max );
953 v3_muladds( s->state.throw_v, dir, -amt, s->state.throw_v );
954 }
955 }
956
957 VG_STATIC void skate_apply_cog_model( player_interface *player,
958 struct player_device_skate *s )
959 {
960 v3f ideal_cog, ideal_diff;
961 v3_muladds( player->rb.co, player->rb.to_world[1],
962 1.0f-player->input_grab->axis.value, ideal_cog );
963 v3_sub( ideal_cog, s->state.cog, ideal_diff );
964
965 /* Apply velocities */
966 v3f rv;
967 v3_sub( player->rb.v, s->state.cog_v, rv );
968
969 v3f F;
970 v3_muls( ideal_diff, -k_cog_spring * k_rb_rate, F );
971 v3_muladds( F, rv, -k_cog_damp * k_rb_rate, F );
972
973 float ra = k_cog_mass_ratio,
974 rb = 1.0f-k_cog_mass_ratio;
975
976 /* Apply forces & intergrate */
977 v3_muladds( s->state.cog_v, F, -rb, s->state.cog_v );
978 s->state.cog_v[1] += -9.8f * k_rb_delta;
979 v3_muladds( s->state.cog, s->state.cog_v, k_rb_delta, s->state.cog );
980 }
981
982 VG_STATIC void skate_collision_response( player_interface *player,
983 struct player_device_skate *s,
984 rb_ct *manifold, int len )
985 {
986 for( int j=0; j<10; j++ )
987 {
988 for( int i=0; i<len; i++ )
989 {
990 struct contact *ct = &manifold[i];
991
992 v3f dv, delta;
993 v3_sub( ct->co, player->rb.co, delta );
994 v3_cross( player->rb.w, delta, dv );
995 v3_add( player->rb.v, dv, dv );
996
997 float vn = -v3_dot( dv, ct->n );
998 vn += ct->bias;
999
1000 float temp = ct->norm_impulse;
1001 ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
1002 vn = ct->norm_impulse - temp;
1003
1004 v3f impulse;
1005 v3_muls( ct->n, vn, impulse );
1006
1007 if( fabsf(v3_dot( impulse, player->rb.to_world[2] )) > 10.0f ||
1008 fabsf(v3_dot( impulse, player->rb.to_world[1] )) > 50.0f )
1009 {
1010 /* FIXME */
1011 #if 0
1012 player_kill();
1013 return;
1014 #endif
1015 }
1016
1017 v3_add( impulse, player->rb.v, player->rb.v );
1018 v3_cross( delta, impulse, impulse );
1019
1020 /*
1021 * W Impulses are limited to the Y and X axises, we don't really want
1022 * roll angular velocities being included.
1023 *
1024 * Can also tweak the resistance of each axis here by scaling the wx,wy
1025 * components.
1026 */
1027
1028 float wy = v3_dot( player->rb.to_world[1], impulse ) * 0.8f,
1029 wx = v3_dot( player->rb.to_world[0], impulse ) * 1.0f;
1030
1031 v3_muladds( player->rb.w, player->rb.to_world[1], wy, player->rb.w );
1032 v3_muladds( player->rb.w, player->rb.to_world[0], wx, player->rb.w );
1033 }
1034 }
1035 }
1036
1037 VG_STATIC void skate_integrate( player_interface *player,
1038 struct player_device_skate *s )
1039 {
1040 /* integrate rigidbody velocities */
1041 v3f gravity = { 0.0f, -9.6f, 0.0f };
1042 v3_muladds( player->rb.v, gravity, k_rb_delta, player->rb.v );
1043 v3_muladds( player->rb.co, player->rb.v, k_rb_delta, player->rb.co );
1044
1045 v3_lerp( player->rb.w, (v3f){0.0f,0.0f,0.0f}, 0.125f*0.5f, player->rb.w );
1046 if( v3_length2( player->rb.w ) > 0.0f )
1047 {
1048 v4f rotation;
1049 v3f axis;
1050 v3_copy( player->rb.w, axis );
1051
1052 float mag = v3_length( axis );
1053 v3_divs( axis, mag, axis );
1054 q_axis_angle( rotation, axis, mag*k_rb_delta );
1055 q_mul( rotation, player->rb.q, player->rb.q );
1056 }
1057
1058 /* integrate steering velocities */
1059 v4f rotate;
1060 float l = (s->state.activity == k_skate_activity_air)? 0.04f: 0.3f;
1061
1062 s->state.steery_s = vg_lerpf( s->state.steery_s, s->state.steery, l );
1063 s->state.steerx_s = vg_lerpf( s->state.steerx_s, s->state.steerx, l );
1064
1065 q_axis_angle( rotate, player->rb.to_world[1], s->state.steery_s );
1066 q_mul( rotate, player->rb.q, player->rb.q );
1067
1068 q_axis_angle( rotate, player->rb.to_world[0], s->state.steerx_s );
1069 q_mul( rotate, player->rb.q, player->rb.q );
1070
1071 s->state.steerx = 0.0f;
1072 s->state.steery = 0.0f;
1073
1074 #if 0
1075 v3_sub( player.rb.v, s->phys.v_prev, s->phys.a );
1076 v3_muls( s->phys.a, 1.0f/VG_TIMESTEP_FIXED, s->phys.a );
1077 v3_copy( player.rb.v, s->phys.v_prev );
1078 #endif
1079
1080 rb_update_transform( &player->rb );
1081 }
1082
1083 VG_STATIC void player_skate_update( player_interface *player,
1084 player_attachment *at )
1085 {
1086 struct player_device_skate *s = at->storage;
1087 s->state.activity_prev = s->state.activity;
1088
1089 /* Setup colliders */
1090 m4x3f mtx_front, mtx_back;
1091 m3x3_identity( mtx_front );
1092 m3x3_identity( mtx_back );
1093
1094 skate_get_board_points( player, s, mtx_front[3], mtx_back[3] );
1095
1096 s->sphere_back.radius = 0.3f;
1097 s->sphere_front.radius = 0.3f;
1098
1099 /* create manifold(s) */
1100 rb_ct manifold[72],
1101 *interface_manifold = NULL,
1102 *grind_manifold = NULL;
1103
1104 int
1105 len_front = skate_collide_smooth( player, mtx_front,
1106 &s->sphere_front, manifold ),
1107 len_back = skate_collide_smooth( player, mtx_back,
1108 &s->sphere_back, &manifold[len_front] ),
1109
1110 interface_len = len_front + len_back;
1111
1112 interface_manifold = manifold;
1113 grind_manifold = manifold + interface_len;
1114
1115 int grind_len = skate_grind_collide( player, at, grind_manifold );
1116
1117 for( int i=0; i<interface_len+grind_len; i ++ )
1118 {
1119 rb_prepare_contact( &manifold[i] );
1120 rb_debug_contact( &manifold[i] );
1121 }
1122
1123 skate_apply_grind_model( player, s, grind_manifold, grind_len );
1124 skate_apply_interface_model( player, s, manifold, interface_len );
1125
1126 skate_apply_pump_model( player, s );
1127 skate_apply_cog_model( player, s );
1128 skate_collision_response( player, s, manifold, interface_len + grind_len );
1129
1130 skate_apply_grab_model( player, s );
1131 skate_apply_friction_model( player, s );
1132 skate_apply_jump_model( player, s );
1133 skate_apply_air_model( player, s );
1134
1135 skate_integrate( player, s );
1136 }
1137
1138 VG_STATIC void player_skate_post_update( player_interface *player,
1139 player_attachment *at )
1140 {
1141 }
1142
1143 VG_STATIC void player_skate_ui( player_interface *player,
1144 player_attachment *at )
1145 {
1146 struct player_device_skate *s = at->storage;
1147
1148 /* FIXME: Compression */
1149 player_debugtext( 1, "V: %5.2f %5.2f %5.2f",player->rb.v[0],
1150 player->rb.v[1],
1151 player->rb.v[2] );
1152 player_debugtext( 1, "CO: %5.2f %5.2f %5.2f",player->rb.co[0],
1153 player->rb.co[1],
1154 player->rb.co[2] );
1155 player_debugtext( 1, "W: %5.2f %5.2f %5.2f",player->rb.w[0],
1156 player->rb.w[1],
1157 player->rb.w[2] );
1158
1159 player_debugtext( 1, "activity: %s\n",
1160 (const char *[]){ "k_skate_activity_air",
1161 "k_skate_activity_ground",
1162 "k_skate_activity_grind }" }
1163 [s->state.activity] );
1164 }
1165
1166 VG_STATIC void player_skate_pose( player_interface *player,
1167 player_attachment *at,
1168 player_pose pose, m4x3f transform )
1169 {
1170 struct player_device_skate *s = at->storage;
1171 struct player_avatar *av = player->playeravatar;
1172 struct skeleton *sk = &av->sk;
1173
1174 /* Camera position */
1175 /* TODO split up */
1176 /* FIXME */
1177
1178 #if 0
1179 v3_muladds( phys->m, phys->a, VG_TIMESTEP_FIXED, phys->m );
1180 v3_lerp( phys->m, (v3f){0.0f,0.0f,0.0f}, 0.1f, phys->m );
1181
1182 phys->m[0] = vg_clampf( phys->m[0], -2.0f, 2.0f );
1183 phys->m[1] = vg_clampf( phys->m[1], -2.0f, 2.0f );
1184 phys->m[2] = vg_clampf( phys->m[2], -2.0f, 2.0f );
1185 v3_lerp( phys->bob, phys->m, 0.2f, phys->bob );
1186 #endif
1187
1188 /* Head */
1189 float kheight = 2.0f,
1190 kleg = 0.6f;
1191
1192 v3f offset;
1193 v3_zero( offset );
1194
1195 #if 0
1196 m3x3_mulv( player.inv_visual_transform, phys->bob, offset );
1197 #endif
1198
1199 static float speed_wobble = 0.0f, speed_wobble_2 = 0.0f;
1200
1201 float curspeed = v3_length( player->rb.v ),
1202 kickspeed = vg_clampf( curspeed*(1.0f/40.0f), 0.0f, 1.0f ),
1203 kicks = (vg_randf()-0.5f)*2.0f*kickspeed,
1204 sign = vg_signf( kicks );
1205
1206 s->wobble[0] = vg_lerpf( s->wobble[0], kicks*kicks*sign, 6.0f*vg.time_delta);
1207 s->wobble[1] = vg_lerpf( s->wobble[1], speed_wobble, 2.4f*vg.time_delta);
1208
1209 offset[0] *= 0.26f;
1210 offset[0] += speed_wobble_2*3.0f;
1211
1212 offset[1] *= -0.3f;
1213 offset[2] *= 0.01f;
1214
1215 offset[0]=vg_clampf(offset[0],-0.8f,0.8f)*(1.0f-fabsf(s->blend_slide)*0.9f);
1216 offset[1]=vg_clampf(offset[1],-0.5f,0.0f);
1217
1218 /*
1219 * Animation blending
1220 * ===========================================
1221 */
1222
1223 /* sliding */
1224 {
1225 float desired = vg_clampf( fabsf( s->state.slip ), 0.0f, 1.0f );
1226 s->blend_slide = vg_lerpf( s->blend_slide, desired, 2.4f*vg.time_delta);
1227 }
1228
1229 /* movement information */
1230 {
1231 int iair = (s->state.activity == k_skate_activity_air) ||
1232 (s->state.activity == k_skate_activity_grind );
1233
1234 float dirz = s->state.reverse > 0.0f? 0.0f: 1.0f,
1235 dirx = s->state.slip < 0.0f? 0.0f: 1.0f,
1236 fly = iair? 1.0f: 0.0f;
1237
1238 s->blend_z = vg_lerpf( s->blend_z, dirz, 2.4f*vg.time_delta );
1239 s->blend_x = vg_lerpf( s->blend_x, dirx, 0.6f*vg.time_delta );
1240 s->blend_fly = vg_lerpf( s->blend_fly, fly, 2.4f*vg.time_delta );
1241 }
1242
1243 mdl_keyframe apose[32], bpose[32];
1244 mdl_keyframe ground_pose[32];
1245 {
1246 /* when the player is moving fast he will crouch down a little bit */
1247 float stand = 1.0f - vg_clampf( curspeed * 0.03f, 0.0f, 1.0f );
1248 s->blend_stand = vg_lerpf( s->blend_stand, stand, 6.0f*vg.time_delta );
1249
1250 /* stand/crouch */
1251 float dir_frame = s->blend_z * (15.0f/30.0f),
1252 stand_blend = offset[1]*-2.0f;
1253
1254 v3f local_cog;
1255 m4x3_mulv( player->rb.to_local, s->state.cog, local_cog );
1256
1257 stand_blend = vg_clampf( 1.0f-local_cog[1], 0, 1 );
1258
1259 skeleton_sample_anim( sk, s->anim_stand, dir_frame, apose );
1260 skeleton_sample_anim( sk, s->anim_highg, dir_frame, bpose );
1261 skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
1262
1263 /* sliding */
1264 float slide_frame = s->blend_x * (15.0f/30.0f);
1265 skeleton_sample_anim( sk, s->anim_slide, slide_frame, bpose );
1266 skeleton_lerp_pose( sk, apose, bpose, s->blend_slide, apose );
1267
1268 /* pushing */
1269 double push_time = vg.time - s->state.start_push;
1270 s->blend_push = vg_lerpf( s->blend_push,
1271 (vg.time - s->state.cur_push) < 0.125,
1272 6.0f*vg.time_delta );
1273
1274 float pt = push_time + vg.accumulator;
1275 if( s->state.reverse > 0.0f )
1276 skeleton_sample_anim( sk, s->anim_push, pt, bpose );
1277 else
1278 skeleton_sample_anim( sk, s->anim_push_reverse, pt, bpose );
1279
1280 skeleton_lerp_pose( sk, apose, bpose, s->blend_push, apose );
1281
1282 /* trick setup */
1283 float jump_start_frame = 14.0f/30.0f;
1284
1285 float charge = s->state.jump_charge;
1286 s->blend_jump = vg_lerpf( s->blend_jump, charge, 8.4f*vg.time_delta );
1287
1288 float setup_frame = charge * jump_start_frame,
1289 setup_blend = vg_minf( s->blend_jump, 1.0f );
1290
1291 float jump_frame = (vg.time - s->state.jump_time) + jump_start_frame;
1292 if( jump_frame >= jump_start_frame && jump_frame <= (40.0f/30.0f) )
1293 setup_frame = jump_frame;
1294
1295 struct skeleton_anim *jump_anim = s->state.jump_dir?
1296 s->anim_ollie:
1297 s->anim_ollie_reverse;
1298
1299 skeleton_sample_anim_clamped( sk, jump_anim, setup_frame, bpose );
1300 skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
1301 }
1302
1303 mdl_keyframe air_pose[32];
1304 {
1305 float target = -player->input_js1h->axis.value;
1306 s->blend_airdir = vg_lerpf( s->blend_airdir, target, 2.4f*vg.time_delta );
1307
1308 float air_frame = (s->blend_airdir*0.5f+0.5f) * (15.0f/30.0f);
1309 skeleton_sample_anim( sk, s->anim_air, air_frame, apose );
1310
1311 static v2f grab_choice;
1312
1313 v2f grab_input = { player->input_js2h->axis.value,
1314 player->input_js2v->axis.value };
1315 v2_add( s->state.grab_mouse_delta, grab_input, grab_input );
1316 if( v2_length2( grab_input ) <= 0.001f )
1317 grab_input[0] = -1.0f;
1318 else
1319 v2_normalize_clamp( grab_input );
1320 v2_lerp( grab_choice, grab_input, 2.4f*vg.time_delta, grab_choice );
1321
1322 float ang = atan2f( grab_choice[0], grab_choice[1] ),
1323 ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
1324 grab_frame = ang_unit * (15.0f/30.0f);
1325
1326 skeleton_sample_anim( sk, s->anim_grabs, grab_frame, bpose );
1327 skeleton_lerp_pose( sk, apose, bpose, s->state.grabbing, air_pose );
1328 }
1329
1330 skeleton_lerp_pose( sk, ground_pose, air_pose, s->blend_fly, pose );
1331
1332 float add_grab_mod = 1.0f - s->blend_fly;
1333
1334 /* additive effects */
1335 {
1336 u32 apply_to[] = { av->id_hip,
1337 av->id_ik_hand_l,
1338 av->id_ik_hand_r,
1339 av->id_ik_elbow_l,
1340 av->id_ik_elbow_r };
1341
1342 for( int i=0; i<vg_list_size(apply_to); i ++ )
1343 {
1344 pose[apply_to[i]-1].co[0] += offset[0]*add_grab_mod;
1345 pose[apply_to[i]-1].co[2] += offset[2]*add_grab_mod;
1346 }
1347
1348 mdl_keyframe *kf_board = &pose[av->id_board-1],
1349 *kf_foot_l = &pose[av->id_ik_foot_l-1],
1350 *kf_foot_r = &pose[av->id_ik_foot_r-1];
1351
1352 v3f bo;
1353 v3_muls( s->board_offset, add_grab_mod, bo );
1354
1355 v3_add( bo, kf_board->co, kf_board->co );
1356 v3_add( bo, kf_foot_l->co, kf_foot_l->co );
1357 v3_add( bo, kf_foot_r->co, kf_foot_r->co );
1358
1359 m3x3f c;
1360 q_m3x3( s->board_rotation, c );
1361
1362 v3f d;
1363 v3_sub( kf_foot_l->co, bo, d );
1364 m3x3_mulv( c, d, d );
1365 v3_add( bo, d, kf_foot_l->co );
1366
1367 v3_sub( kf_foot_r->co, bo, d );
1368 m3x3_mulv( c, d, d );
1369 v3_add( bo, d, kf_foot_r->co );
1370
1371 q_mul( s->board_rotation, kf_board->q, kf_board->q );
1372 q_normalize( kf_board->q );
1373 }
1374
1375 /* transform */
1376 rb_extrapolate_transform( &player->rb, transform );
1377
1378 #if 0
1379 v3_muladds( player.visual_transform[3], phys->rb.up, -0.2f,
1380 player.visual_transform[3] );
1381 #endif
1382
1383 v4f qresy, qresx, qresidual;
1384 m3x3f mtx_residual;
1385 float substep = vg_clampf( vg.accumulator / VG_TIMESTEP_FIXED, 0.0f, 1.0f );
1386 q_axis_angle( qresy, player->rb.to_world[1], s->state.steery_s*substep );
1387 q_axis_angle( qresx, player->rb.to_world[0], s->state.steerx_s*substep );
1388
1389 q_mul( qresy, qresx, qresidual );
1390 q_m3x3( qresidual, mtx_residual );
1391 m3x3_mul( transform, mtx_residual, transform );
1392 }
1393
1394 VG_STATIC void player_skate_get_camera( player_interface *player,
1395 player_attachment *at, camera *cam )
1396 {
1397 struct player_device_skate *s = at->storage;
1398 struct player_avatar *av = player->playeravatar;
1399
1400 /* FIXME: viewpoint entity */
1401 v3f vp = {-0.1f,1.8f,0.0f};
1402 m4x3_mulv( av->sk.final_mtx[ av->id_head-1 ], vp, cam->pos );
1403
1404 v3_zero( cam->angles );
1405 cam->fov = 119.0f;
1406
1407 /* TODO: smooth clamp lerp rate of change */
1408
1409 v3_lerp( s->state.vl, player->rb.v, 5.0f*vg.time_delta, s->state.vl );
1410
1411 float *v = s->state.vl,
1412 yaw = atan2f( v[0], -v[2] ),
1413 pitch = atan2f
1414 (
1415 -v[1],
1416 sqrtf
1417 (
1418 v[0]*v[0] + v[2]*v[2]
1419 )
1420 )
1421 * 0.7f + 0.5f;
1422
1423 cam->angles[0] = yaw;
1424 cam->angles[1] = pitch;
1425 }
1426
1427 VG_STATIC void player_skate_transport( player_interface *player,
1428 player_attachment *at,
1429 teleport_gate *gate )
1430 {
1431 struct player_device_skate *s = at->storage;
1432
1433 m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
1434 m4x3_mulv( gate->transport, s->state.cog, s->state.cog );
1435 m3x3_mulv( gate->transport, s->state.cog_v, s->state.cog_v );
1436 m3x3_mulv( gate->transport, player->rb.v, player->rb.v );
1437 m3x3_mulv( gate->transport, s->state.vl, s->state.vl );
1438
1439 v4f transport_rotation;
1440 m3x3_q( gate->transport, transport_rotation );
1441 q_mul( transport_rotation, player->rb.q, player->rb.q );
1442
1443 s->state_gate_storage = s->state;
1444 }
1445
1446 VG_STATIC player_device player_device_skate =
1447 {
1448 .pre_update = player_skate_pre_update,
1449 .update = player_skate_update,
1450 .post_update = player_skate_post_update,
1451 .get_camera = player_skate_get_camera,
1452 .debug_ui = player_skate_ui,
1453 .bind = player_skate_bind,
1454 .pose = player_skate_pose,
1455 .gate_transport= player_skate_transport
1456 };
1457
1458 #endif /* PLAYER_DEVICE_SKATE_H */