7373660b9fcac4dc9bba1c116b5e693e03badb28
[vg.git] / labs / physics.c
1 #define VG_GAME
2 #define VG_AUDIO_FORCE_COMPRESSED
3 #define VG_3D
4 #define VG_LOG_SOURCE_INFO
5 #define VG_TIMESTEP_FIXED (1.0/60.0)
6
7 #ifndef VG_RELEASE
8 #define VG_DEVWINDOW
9 #endif
10
11 #define SDL_MAIN_HANDLED
12
13 #define VG_MAX_CONTACTS 2048
14
15 #include "vg/vg.h"
16 #include "vg/vg_camera.h"
17 #include "vg/vg_rigidbody.h"
18 #include "vg/vg_rigidbody_collision.h"
19 #include "vg/vg_profiler.h"
20 #include "vg/vg_bvh.h"
21
22 #define SHAPE_MAX 256
23 static rigidbody shapes[SHAPE_MAX];
24 static rb_capsule shapes_inf[SHAPE_MAX];
25 static v4f shapes_colour[SHAPE_MAX];
26 static boxf shapes_bbx[SHAPE_MAX];
27
28 static boxf floor_box = {{-6.999,-2.001,-6.999},{6.999,-0.999,6.999}};
29
30 static rigidbody racket;
31
32 static f32 k_iterations = 8.0f,
33 k_view_x = 0.0f,
34 k_view_y = 0.2f,
35 k_view_z = 10.0f,
36 k_shapes = 32.0f,
37 k_racket_d = 8.0f;
38
39 static rb_capsule racket_ca = { .h = 4.3f, .r = 0.6f },
40 racket_cb = { .h = 5.5f, .r = 0.9f };
41 static m4x3f racket_a_mdl, racket_b_mdl;
42 static m3x3f racket_I;
43
44 static v3f k_racket_init_w;
45 static i32 k_demo = 0;
46 static i32 k_gyro = 0;
47 static i32 k_prof_normalize = 0;
48 static i32 k_bbx = 1;
49 static i32 k_spacial = 0;
50
51 static struct vg_profile prof_refit = { .name = "Refit" },
52 prof_broad = { .name = "Broad phase",
53 .mode = k_profile_mode_accum },
54 prof_narrow = { .name = "Narrow phase",
55 .mode = k_profile_mode_accum },
56 prof_solve = { .name = "Solver" };
57
58 static void shape_bvh_expand_bound( void *user, boxf bound, u32 item_index ){
59 box_concat( bound, shapes_bbx[item_index] );
60 }
61
62 static f32 shape_bvh_centroid( void *user, u32 item_index, int axis ){
63 f32 x = shapes_bbx[item_index][0][axis] + shapes_bbx[item_index][1][axis];
64 return x*0.5f;
65 }
66
67 static void shape_bvh_closest( void *user, u32 item_index,
68 v3f point, v3f closest ){
69 closest_point_aabb( point, shapes_bbx[item_index], closest );
70 }
71
72 static void shape_bvh_swap( void *user, u32 ia, u32 ib ){
73 rigidbody temp = shapes[ib];
74 shapes[ib] = shapes[ia];
75 shapes[ia] = temp;
76
77 rb_capsule cb = shapes_inf[ib];
78 shapes_inf[ib] = shapes_inf[ia];
79 shapes_inf[ia] = cb;
80
81 v4f colourb;
82 v4_copy( shapes_colour[ib], colourb );
83 v4_copy( shapes_colour[ia], shapes_colour[ib] );
84 v4_copy( colourb, shapes_colour[ia] );
85
86 boxf boxb;
87 box_copy( shapes_bbx[ib], boxb );
88 box_copy( shapes_bbx[ia], shapes_bbx[ib] );
89 box_copy( boxb, shapes_bbx[ia] );
90 }
91
92 static bh_system shape_bvh = {
93 .expand_bound = shape_bvh_expand_bound,
94 .item_centroid = shape_bvh_centroid,
95 .item_closest = shape_bvh_closest,
96 .item_swap = shape_bvh_swap
97 };
98
99 static bh_tree *shape_bvh_tree = NULL;
100
101 int main( int argc, char *argv[] ){
102 vg_mem.use_libc_malloc = 0;
103 vg_set_mem_quota( 80*1024*1024 );
104 vg_enter( argc, argv, "Voyager Game Engine" );
105 return 0;
106 }
107
108 static void vg_launch_opt(void){
109 const char *arg;
110 }
111
112 static void vg_preload(void){
113 vg_audio.dsp_enabled = 0;
114 }
115
116 static void init_random(void){
117 for( u32 i=0; i<(u32)k_shapes; i ++ ){
118 f32 h = vg_randf64( &vg.rand ) * 2.0f + 1.3f,
119 r = vg_randf64( &vg.rand ) * 0.5f + 0.125f,
120 pv = vg_capsule_volume( r, h ),
121 k_density = 8.0f,
122 pm = pv * k_density;
123
124 shapes_inf[i].r = r;
125 shapes_inf[i].h = h;
126
127 m3x3f pI;
128 vg_capsule_inertia( r, h, pm, pI );
129 m3x3_inv( pI, shapes[i].iI );
130 shapes[i].inv_mass = 1.0f / pm;
131
132 v3f dir;
133 vg_rand_dir( &vg.rand, dir );
134 q_axis_angle( shapes[i].q, dir, vg_randf64(&vg.rand)*VG_TAUf );
135 vg_rand_sphere( &vg.rand, shapes[i].co );
136 v3_muladds( (v3f){0,4,0}, shapes[i].co, 4.0f, shapes[i].co );
137 v3_zero( shapes[i].v );
138 v3_zero( shapes[i].w );
139 shapes_colour[i][0] = vg_randf64(&vg.rand);
140 shapes_colour[i][1] = vg_randf64(&vg.rand);
141 shapes_colour[i][2] = vg_randf64(&vg.rand);
142 shapes_colour[i][3] = 1.0f;
143 rb_update_matrices( &shapes[i] );
144 }
145 }
146
147 static void init_racket(void){
148 f32 ma = vg_capsule_volume( racket_ca.r, racket_ca.h ) * k_racket_d,
149 mb = vg_capsule_volume( racket_cb.r, racket_cb.h ) * k_racket_d,
150 mt = ma+mb;
151 m3x3f aI, bI;
152
153 /* tensor for A */
154 vg_capsule_inertia( racket_ca.r, racket_ca.h, ma, aI );
155 m4x3_identity( racket_a_mdl );
156 racket_a_mdl[3][1] = -racket_ca.h*0.5f*(mb/mt);
157 vg_translate_inertia( aI, ma, racket_a_mdl[3] );
158
159 /* tensor for B */
160 vg_capsule_inertia( racket_cb.r, racket_cb.h, mb, bI );
161
162 v4f q;
163 q_axis_angle( q, (v4f){1,0,0}, VG_TAUf*0.25f );
164
165 m4x3_identity( racket_b_mdl );
166 q_m3x3( q, racket_b_mdl );
167 vg_rotate_inertia( bI, racket_b_mdl );
168 racket_b_mdl[3][1] = racket_ca.h*0.5f*(ma/mt);
169 vg_translate_inertia( bI, mb, racket_b_mdl[3] );
170
171 m3x3_add( aI, bI, racket_I );
172 m3x3_inv( racket_I, racket.iI );
173 racket.inv_mass = 1.0f/(mb+ma);
174 }
175
176 static void reset_racket(void){
177 q_identity( racket.q );
178 v3_zero( racket.co );
179 v3_copy( k_racket_init_w, racket.w );
180 v3_zero( racket.v );
181 rb_update_matrices( &racket );
182 }
183
184 static void vg_load(void){
185 vg_bake_shaders();
186 init_random();
187 shape_bvh_tree = bh_create( NULL, &shape_bvh, NULL, SHAPE_MAX, 1 );
188 init_racket();
189 reset_racket();
190 }
191
192 static void vg_pre_update(void){
193 vg_console.cheats = 1;
194 vg_lines.render = 1;
195 }
196
197 static void demo0_refit(void){
198 if( k_spacial == 0 ) return;
199
200 for( u32 i=0; i<(u32)k_shapes; i ++ ){
201 f32 h = shapes_inf[i].h,
202 r = shapes_inf[i].r;
203
204 rigidbody *rb = &shapes[i];
205
206 v3f p0, p1;
207 v3_muladds( rb->to_world[3], rb->to_world[1], -h*0.5f+r, p0 );
208 v3_muladds( rb->to_world[3], rb->to_world[1], h*0.5f-r, p1 );
209
210 v3f *bbx = shapes_bbx[i];
211 v3_minv( p0, p1, bbx[0] );
212 v3_maxv( p0, p1, bbx[1] );
213 v3_muladds( bbx[0], (v3f){-1,-1,-1}, r, bbx[0] );
214 v3_muladds( bbx[1], (v3f){ 1, 1, 1}, r, bbx[1] );
215 }
216
217 if( k_spacial == 1 ) return;
218
219 if( k_spacial == 2 )
220 bh_rebuild( shape_bvh_tree, (u32)k_shapes );
221 }
222
223 static void demo0(void){
224 vg_profile_begin( &prof_refit );
225 demo0_refit();
226 vg_profile_end( &prof_refit );
227
228 static rigidbody _null,
229 _mover;
230 rb_solver_reset();
231
232 f32 t = vg.time * 0.1f * VG_TAUf;
233 v3f sphere_pos = { sinf(t)*2.0f, -1, cosf(t)*2.0f };
234 _mover.v[0] = (sinf(t+vg.time_fixed_delta)-sinf(t))*2.0f;
235 _mover.v[2] = (cosf(t+vg.time_fixed_delta)-cosf(t))*2.0f;
236
237 for( u32 i=0; i<(u32)k_shapes; i ++ ){
238 rigidbody *rbi = &shapes[i];
239 rb_capsule *infi = &shapes_inf[i];
240 v3f *bbxi = shapes_bbx[i];
241
242 if( rb_global_has_space() ){
243 rb_ct *buf = rb_global_buffer();
244 m4x3f mtx;
245 m4x3_identity( mtx );
246 u32 l = rb_capsule__box( rbi->to_world, infi,
247 mtx, mtx, floor_box, buf );
248
249 for( u32 k=0; k<l; k ++ ){
250 buf[k].rba = rbi;
251 buf[k].rbb = &_null;
252 }
253
254 rb_contact_count += l;
255 }
256 else break;
257
258 if( rb_global_has_space() ){
259 rb_ct *buf = rb_global_buffer();
260 u32 l = rb_capsule__sphere( rbi->to_world, infi, sphere_pos, 1, buf );
261
262 for( u32 k=0; k<l; k ++ ){
263 buf[k].rba = rbi;
264 buf[k].rbb = &_mover;
265 }
266
267 rb_contact_count += l;
268 }
269
270 if( k_spacial == 2 ){
271 bh_iter it;
272 bh_iter_init_box( 0, &it, bbxi );
273 i32 idx;
274
275 while(1){
276 vg_profile_begin( &prof_broad );
277 if( !bh_next( shape_bvh_tree, &it, &idx ) ){
278 vg_profile_end( &prof_broad );
279 break;
280 }
281 vg_profile_end( &prof_broad );
282
283 if( idx <= i ) continue;
284
285 vg_profile_begin( &prof_narrow );
286
287 rigidbody *rbj = &shapes[idx];
288 v3f *bbxj = shapes_bbx[idx];
289 rb_capsule *infj = &shapes_inf[idx];
290
291 if( rb_global_has_space() ){
292 rb_ct *buf = rb_global_buffer();
293 u32 l = rb_capsule__capsule( rbi->to_world, infi,
294 rbj->to_world, infj, buf );
295
296 for( u32 k=0; k<l; k ++ ){
297 buf[k].rba = rbi;
298 buf[k].rbb = rbj;
299 }
300
301 rb_contact_count += l;
302 }
303
304 vg_profile_end( &prof_narrow );
305 }
306 }
307 else {
308 if( i == ((u32)k_shapes)-1 ){
309 break;
310 }
311 for( u32 j=i+1; j<(u32)k_shapes; j ++ ){
312 rigidbody *rbj = &shapes[j];
313 v3f *bbxj = shapes_bbx[j];
314 rb_capsule *infj = &shapes_inf[j];
315
316 if( k_spacial == 1 ){
317 vg_profile_begin( &prof_broad );
318 if( !box_overlap( bbxi, bbxj ) ){
319 vg_profile_end( &prof_broad );
320 continue;
321 }
322 }
323
324 vg_profile_begin( &prof_narrow );
325 if( rb_global_has_space() ){
326 rb_ct *buf = rb_global_buffer();
327 u32 l = rb_capsule__capsule( rbi->to_world, infi,
328 rbj->to_world, infj, buf );
329
330 for( u32 k=0; k<l; k ++ ){
331 buf[k].rba = rbi;
332 buf[k].rbb = rbj;
333 }
334
335 rb_contact_count += l;
336 }
337 vg_profile_end( &prof_narrow );
338 }
339 }
340 }
341
342 vg_profile_increment( &prof_broad );
343 vg_profile_increment( &prof_narrow );
344
345 vg_profile_begin( &prof_solve );
346 rb_presolve_contacts( rb_contact_buffer, rb_contact_count );
347 for( u32 i=0; i<(u32)k_iterations; i ++ )
348 rb_solve_contacts( rb_contact_buffer, rb_contact_count );
349
350 for( u32 i=0; i<(u32)k_shapes; i ++ ){
351 rigidbody *rbi = &shapes[i];
352 if( k_gyro ){
353 m3x3f I;
354 m3x3_inv( rbi->iI, I );
355 rb_solve_gyroscopic( rbi, I, vg.time_fixed_delta );
356 }
357 rb_iter( rbi );
358 rb_update_matrices( rbi );
359 }
360 vg_profile_end( &prof_solve );
361 }
362
363 static void demo1(void){
364 vg_profile_begin( &prof_refit );
365 vg_profile_end( &prof_refit );
366
367 vg_profile_increment( &prof_broad );
368 vg_profile_increment( &prof_narrow );
369
370 vg_profile_begin( &prof_solve );
371
372 v3_muladds( racket.v, (v3f){0,9.8f,0}, vg.time_fixed_delta, racket.v );
373 if( k_gyro ) rb_solve_gyroscopic( &racket, racket_I, vg.time_fixed_delta );
374 rb_iter( &racket );
375 rb_update_matrices( &racket );
376
377 vg_profile_end( &prof_solve );
378 }
379
380 static void vg_fixed_update(void){
381 if( k_demo == 0 ) demo0();
382 else if( k_demo == 1 ) demo1();
383 }
384
385 static void vg_post_update(void){
386 if( vg_getkey( SDLK_8 ) )
387 init_random();
388 }
389
390 static void vg_framebuffer_resize( int w, int h ){
391 }
392
393 static void draw_origin_axis(void){
394 vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 1.0f, 0.0f, 0.0f }, 0xffff0000 );
395 vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 1.0f, 0.0f }, 0xff00ff00 );
396 vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 0.0f, 1.0f }, 0xff0000ff );
397 }
398
399 static void render0(void){
400 f32 t = vg.time * 0.1f * VG_TAUf;
401 m4x3f mdl;
402 m4x3_identity( mdl );
403 vg_rb_view_box( mdl, floor_box, (v4f){0.8f,0.8f,0.8f,1} );
404
405 mdl[3][0] = sinf(t)*2.0f;
406 mdl[3][1] = -1;
407 mdl[3][2] = cosf(t)*2.0f;
408 vg_rb_view_sphere( mdl, 1, (v4f){0,1,0,1} );
409
410 for( u32 i=0; i<(u32)k_shapes; i ++ ){
411 rigidbody *rbi = &shapes[i];
412 rb_capsule *infi = &shapes_inf[i];
413 f32 *coli = shapes_colour[i];
414
415 v4f q;
416 v3f co;
417 rb_extrapolate( rbi, co, q );
418 q_m3x3( q, mdl );
419 v3_copy( co, mdl[3] );
420 vg_rb_view_capsule( mdl, infi->r, infi->h, coli );
421 }
422
423 if( k_spacial && k_bbx ){
424 for( u32 i=0; i<(u32)k_shapes; i ++ ){
425 vg_line_boxf( shapes_bbx[i], VG__RED );
426 }
427
428 if( k_spacial == 2 ){
429 bh_debug_trace( shape_bvh_tree, 0, (v3f){0,0,0}, VG__GREEN );
430 }
431 }
432 }
433
434 static void render1(void){
435 m4x3f mdl, mmdl;
436 v4f q;
437 rb_extrapolate( &racket, mdl[3], q );
438 q_m3x3( q, mdl );
439
440 m4x3_mul( mdl, racket_a_mdl, mmdl );
441 vg_rb_view_capsule( mmdl, racket_ca.r, racket_ca.h, (v4f){1,0,0,1} );
442
443 m4x3_mul( mdl, racket_b_mdl, mmdl );
444 vg_rb_view_capsule( mmdl, racket_cb.r, racket_cb.h, (v4f){0,1,0,1} );
445 }
446
447 static void vg_render(void){
448 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
449 glViewport( 0,0, vg.window_x, vg.window_y );
450 glEnable( GL_DEPTH_TEST );
451 glDisable( GL_BLEND );
452
453 glClearColor( 0.05f, 0.05f, 0.05f, 1.0f );
454 glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
455
456 vg_camera cam = {
457 .angles = { -k_view_x, k_view_y, 0 },
458 .nearz = 0.01f,
459 .farz = 500.0f,
460 .fov = 90.0f,
461 .pos = { sinf(k_view_x)*k_view_z*cosf(k_view_y),
462 sinf(k_view_y)*k_view_z,
463 cosf(k_view_x)*k_view_z*cosf(k_view_y) },
464 };
465
466 vg_camera_update_transform( &cam );
467 vg_camera_update_view( &cam );
468 vg_camera_update_projection( &cam );
469 vg_camera_finalize( &cam );
470 m4x4_copy( cam.mtx.pv, vg.pv );
471
472 vg_rb_view_bind();
473 if( k_demo == 0 ) render0();
474 else if( k_demo == 1 ) render1();
475
476 draw_origin_axis();
477 vg_lines_drawall();
478
479 glDisable(GL_DEPTH_TEST);
480 }
481
482 struct ui_enum_opt spacial_mode_ui_enum[] = {
483 { 0, "None" },
484 { 1, "BBX" },
485 { 2, "BVH - Full rebuild" },
486 { 3, "BVH - Temporal fitting" }
487 };
488
489 static void gui0( ui_rect panel ){
490 ui_slider( panel, "Shapes", 2, vg_list_size(shapes), &k_shapes, "%.0f" );
491
492 if( ui_button( panel, "randomize" ) == k_ui_button_click ){
493 init_random();
494 }
495 }
496
497 static void gui1( ui_rect panel ){
498 ui_rect l, r;
499 ui_standard_widget( panel, l, 1 );
500 ui_split_ratio( l, k_ui_axis_v, 0.5f, 4, l, r );
501 ui_slider( l, "aH", 1.0f, 10.0f, &racket_ca.h, "%.1f" );
502 ui_slider( r, "aR", 0.1f, 10.0f, &racket_ca.r, "%.1f" );
503
504 ui_standard_widget( panel, l, 1 );
505 ui_split_ratio( l, k_ui_axis_v, 0.5f, 4, l, r );
506 ui_slider( l, "bH", 1.0f, 10.0f, &racket_cb.h, "%.1f" );
507 ui_slider( r, "bR", 0.1f, 10.0f, &racket_cb.r, "%.1f" );
508
509 for( u32 i=0; i<3; i ++ ){
510 ui_rect v0,v1,v2;
511 ui_standard_widget( panel, v0, 1 );
512 ui_split_ratio( v0, k_ui_axis_v, 2.0f/3.0f, 4, v0, v2 );
513 ui_split_ratio( v0, k_ui_axis_v, 1.0f/2.0f, 4, v0, v1 );
514
515 char buf[16];
516 snprintf( buf, sizeof(buf), "%.3f", racket_I[i][0] );
517 ui_text( v0, buf, 1, k_ui_align_middle_center, 0 );
518 snprintf( buf, sizeof(buf), "%.3f", racket_I[i][1] );
519 ui_text( v1, buf, 1, k_ui_align_middle_center, 0 );
520 snprintf( buf, sizeof(buf), "%.3f", racket_I[i][2] );
521 ui_text( v2, buf, 1, k_ui_align_middle_center, 0 );
522 }
523
524 init_racket();
525
526 ui_info( panel, "init conditions" );
527 ui_rect v0,v1,v2;
528 ui_standard_widget( panel, v0, 1 );
529 ui_split_ratio( v0, k_ui_axis_v, 2.0f/3.0f, 4, v0, v2 );
530 ui_split_ratio( v0, k_ui_axis_v, 1.0f/2.0f, 4, v0, v1 );
531 ui_slider( v0, "X", 0.01f, 30.0f, k_racket_init_w+0, "%.1f" );
532 ui_slider( v1, "Y", 0.01f, 30.0f, k_racket_init_w+1, "%.1f" );
533 ui_slider( v2, "Z", 0.01f, 30.0f, k_racket_init_w+2, "%.1f" );
534
535 if( ui_button( panel, "init" ) == k_ui_button_click ){
536 reset_racket();
537 }
538 }
539
540 static void vg_gui(void){
541 vg_ui.wants_mouse = 1;
542 ui_rect panel = { vg.window_x-300, 0, 300, vg.window_y };
543 ui_rect_pad( panel, (ui_px[2]){ 8, 8 } );
544
545 ui_rect box;
546 ui_split( panel, k_ui_axis_h, VG_PROFILE_SAMPLE_COUNT*2 +4, 8, box, panel );
547 vg_profile_drawn( (struct vg_profile *[]){ &prof_refit,
548 &prof_broad,
549 &prof_narrow,
550 &prof_solve }, 4,
551 vg.time_fixed_delta*1000.0, box, 0, k_prof_normalize );
552
553 ui_split( panel, k_ui_axis_h, 14*2+8, 4, box, panel );
554 ui_checkbox( panel, "Normalize", &k_prof_normalize );
555
556 ui_slider( panel, "Iterations", 1.0f, 20.0f, &k_iterations, "%.0f" );
557 ui_enum( panel, "Spacial Type", spacial_mode_ui_enum,
558 vg_list_size(spacial_mode_ui_enum), &k_spacial );
559
560 static f32 rate = 60.0f;
561 ui_slider( panel, "Fixed timestep", 10, 200, &rate, "%.1f" );
562 vg.time_fixed_delta = 1.0f/rate;
563
564 ui_checkbox( panel, "Show BBX", &k_bbx );
565 ui_checkbox( panel, "Gyroscopic Term", &k_gyro );
566
567 ui_tabs( panel, panel,
568 (const char *[]){ "collision", "racket" }, 2, &k_demo );
569
570 if( k_demo == 0 ) gui0( panel );
571 else if( k_demo == 1 ) gui1( panel );
572
573 ui_rect viewport = { 0,0, vg.window_x-300, vg.window_y };
574
575 if( ui_inside_rect( viewport, vg_ui.mouse ) && ui_clicking(UI_MOUSE_LEFT) ){
576 k_view_x += -((f32)vg.mouse_delta[0] / (f32)vg.window_x) * VG_TAUf,
577 k_view_y += ((f32)vg.mouse_delta[1] / (f32)vg.window_y) * VG_PIf;
578 }
579 }