2 #define VG_AUDIO_FORCE_COMPRESSED
4 #define VG_LOG_SOURCE_INFO
5 #define VG_TIMESTEP_FIXED (1.0/60.0)
11 #define SDL_MAIN_HANDLED
13 #define VG_MAX_CONTACTS 2048
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"
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
];
28 static boxf floor_box
= {{-6.999,-2.001,-6.999},{6.999,-0.999,6.999}};
30 static rigidbody racket
;
32 static f32 k_iterations
= 8.0f
,
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
;
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;
49 static i32 k_spacial
= 0;
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" };
58 static void shape_bvh_expand_bound( void *user
, boxf bound
, u32 item_index
){
59 box_concat( bound
, shapes_bbx
[item_index
] );
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
];
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
);
72 static void shape_bvh_swap( void *user
, u32 ia
, u32 ib
){
73 rigidbody temp
= shapes
[ib
];
74 shapes
[ib
] = shapes
[ia
];
77 rb_capsule cb
= shapes_inf
[ib
];
78 shapes_inf
[ib
] = shapes_inf
[ia
];
82 v4_copy( shapes_colour
[ib
], colourb
);
83 v4_copy( shapes_colour
[ia
], shapes_colour
[ib
] );
84 v4_copy( colourb
, shapes_colour
[ia
] );
87 box_copy( shapes_bbx
[ib
], boxb
);
88 box_copy( shapes_bbx
[ia
], shapes_bbx
[ib
] );
89 box_copy( boxb
, shapes_bbx
[ia
] );
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
99 static bh_tree
*shape_bvh_tree
= NULL
;
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" );
108 static void vg_launch_opt(void){
112 static void vg_preload(void){
113 vg_audio
.dsp_enabled
= 0;
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
),
128 vg_capsule_inertia( r
, h
, pm
, pI
);
129 m3x3_inv( pI
, shapes
[i
].iI
);
130 shapes
[i
].inv_mass
= 1.0f
/ pm
;
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
] );
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
,
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] );
160 vg_capsule_inertia( racket_cb
.r
, racket_cb
.h
, mb
, bI
);
163 q_axis_angle( q
, (v4f
){1,0,0}, VG_TAUf
*0.25f
);
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] );
171 m3x3_add( aI
, bI
, racket_I
);
172 m3x3_inv( racket_I
, racket
.iI
);
173 racket
.inv_mass
= 1.0f
/(mb
+ma
);
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
);
181 rb_update_matrices( &racket
);
184 static void vg_load(void){
187 shape_bvh_tree
= bh_create( NULL
, &shape_bvh
, NULL
, SHAPE_MAX
, 1 );
192 static void vg_pre_update(void){
193 vg_console
.cheats
= 1;
197 static void demo0_refit(void){
198 if( k_spacial
== 0 ) return;
200 for( u32 i
=0; i
<(u32
)k_shapes
; i
++ ){
201 f32 h
= shapes_inf
[i
].h
,
204 rigidbody
*rb
= &shapes
[i
];
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
);
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] );
217 if( k_spacial
== 1 ) return;
220 bh_rebuild( shape_bvh_tree
, (u32
)k_shapes
);
223 static void demo0(void){
224 vg_profile_begin( &prof_refit
);
226 vg_profile_end( &prof_refit
);
228 static rigidbody _null
,
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
;
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
];
242 if( rb_global_has_space() ){
243 rb_ct
*buf
= rb_global_buffer();
245 m4x3_identity( mtx
);
246 u32 l
= rb_capsule__box( rbi
->to_world
, infi
,
247 mtx
, mtx
, floor_box
, buf
);
249 for( u32 k
=0; k
<l
; k
++ ){
254 rb_contact_count
+= l
;
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
);
262 for( u32 k
=0; k
<l
; k
++ ){
264 buf
[k
].rbb
= &_mover
;
267 rb_contact_count
+= l
;
270 if( k_spacial
== 2 ){
272 bh_iter_init_box( 0, &it
, bbxi
);
276 vg_profile_begin( &prof_broad
);
277 if( !bh_next( shape_bvh_tree
, &it
, &idx
) ){
278 vg_profile_end( &prof_broad
);
281 vg_profile_end( &prof_broad
);
283 if( idx
<= i
) continue;
285 vg_profile_begin( &prof_narrow
);
287 rigidbody
*rbj
= &shapes
[idx
];
288 v3f
*bbxj
= shapes_bbx
[idx
];
289 rb_capsule
*infj
= &shapes_inf
[idx
];
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
);
296 for( u32 k
=0; k
<l
; k
++ ){
301 rb_contact_count
+= l
;
304 vg_profile_end( &prof_narrow
);
308 if( i
== ((u32
)k_shapes
)-1 ){
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
];
316 if( k_spacial
== 1 ){
317 vg_profile_begin( &prof_broad
);
318 if( !box_overlap( bbxi
, bbxj
) ){
319 vg_profile_end( &prof_broad
);
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
);
330 for( u32 k
=0; k
<l
; k
++ ){
335 rb_contact_count
+= l
;
337 vg_profile_end( &prof_narrow
);
342 vg_profile_increment( &prof_broad
);
343 vg_profile_increment( &prof_narrow
);
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
);
350 for( u32 i
=0; i
<(u32
)k_shapes
; i
++ ){
351 rigidbody
*rbi
= &shapes
[i
];
354 m3x3_inv( rbi
->iI
, I
);
355 rb_solve_gyroscopic( rbi
, I
, vg
.time_fixed_delta
);
358 rb_update_matrices( rbi
);
360 vg_profile_end( &prof_solve
);
363 static void demo1(void){
364 vg_profile_begin( &prof_refit
);
365 vg_profile_end( &prof_refit
);
367 vg_profile_increment( &prof_broad
);
368 vg_profile_increment( &prof_narrow
);
370 vg_profile_begin( &prof_solve
);
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
);
375 rb_update_matrices( &racket
);
377 vg_profile_end( &prof_solve
);
380 static void vg_fixed_update(void){
381 if( k_demo
== 0 ) demo0();
382 else if( k_demo
== 1 ) demo1();
385 static void vg_post_update(void){
386 if( vg_getkey( SDLK_8
) )
390 static void vg_framebuffer_resize( int w
, int h
){
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 );
399 static void render0(void){
400 f32 t
= vg
.time
* 0.1f
* VG_TAUf
;
402 m4x3_identity( mdl
);
403 vg_rb_view_box( mdl
, floor_box
, (v4f
){0.8f
,0.8f
,0.8f
,1} );
405 mdl
[3][0] = sinf(t
)*2.0f
;
407 mdl
[3][2] = cosf(t
)*2.0f
;
408 vg_rb_view_sphere( mdl
, 1, (v4f
){0,1,0,1} );
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
];
417 rb_extrapolate( rbi
, co
, q
);
419 v3_copy( co
, mdl
[3] );
420 vg_rb_view_capsule( mdl
, infi
->r
, infi
->h
, coli
);
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
);
428 if( k_spacial
== 2 ){
429 bh_debug_trace( shape_bvh_tree
, 0, (v3f
){0,0,0}, VG__GREEN
);
434 static void render1(void){
437 rb_extrapolate( &racket
, mdl
[3], q
);
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} );
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} );
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
);
453 glClearColor( 0.05f
, 0.05f
, 0.05f
, 1.0f
);
454 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
457 .angles
= { -k_view_x
, k_view_y
, 0 },
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
) },
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
);
473 if( k_demo
== 0 ) render0();
474 else if( k_demo
== 1 ) render1();
479 glDisable(GL_DEPTH_TEST
);
482 struct ui_enum_opt spacial_mode_ui_enum
[] = {
485 { 2, "BVH - Full rebuild" },
486 { 3, "BVH - Temporal fitting" }
489 static void gui0( ui_rect panel
){
490 ui_slider( panel
, "Shapes", 2, vg_list_size(shapes
), &k_shapes
, "%.0f" );
492 if( ui_button( panel
, "randomize" ) == k_ui_button_click
){
497 static void gui1( ui_rect panel
){
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" );
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" );
509 for( u32 i
=0; i
<3; i
++ ){
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
);
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 );
526 ui_info( panel
, "init conditions" );
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" );
535 if( ui_button( panel
, "init" ) == k_ui_button_click
){
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 } );
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
,
551 vg
.time_fixed_delta
*1000.0, box
, 0, k_prof_normalize
);
553 ui_split( panel
, k_ui_axis_h
, 14*2+8, 4, box
, panel
);
554 ui_checkbox( panel
, "Normalize", &k_prof_normalize
);
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
);
560 static f32 rate
= 60.0f
;
561 ui_slider( panel
, "Fixed timestep", 10, 200, &rate
, "%.1f" );
562 vg
.time_fixed_delta
= 1.0f
/rate
;
564 ui_checkbox( panel
, "Show BBX", &k_bbx
);
565 ui_checkbox( panel
, "Gyroscopic Term", &k_gyro
);
567 ui_tabs( panel
, panel
,
568 (const char *[]){ "collision", "racket" }, 2, &k_demo
);
570 if( k_demo
== 0 ) gui0( panel
);
571 else if( k_demo
== 1 ) gui1( panel
);
573 ui_rect viewport
= { 0,0, vg
.window_x
-300, vg
.window_y
};
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
;