6 #include "world_render.h"
8 #include "shaders/model_menu.h"
13 #define MENU_STACK_SIZE 8
16 int active
, credits_open
;
20 u32 page
, /* current page index */
24 k_menu_input_mode_keys
,
25 k_menu_input_mode_mouse
28 f32 mouse_track
, mouse_dist
; /* used for waking up mouse */
31 struct page_stack_frame
{
36 page_stack
[ MENU_STACK_SIZE
];
46 mdl_array_ptr items
, markers
, cameras
;
51 * Attaches memory locations to the various items in the menu
53 static void menu_link(void)
55 /* link data locations */
56 for( u32 i
=0; i
<mdl_arrcount(&menu
.items
); i
++ ){
57 ent_menuitem
*item
= mdl_arritm( &menu
.items
, i
);
59 if( item
->type
== k_ent_menuitem_type_toggle
||
60 item
->type
== k_ent_menuitem_type_slider
){
64 if( item
->type
== k_ent_menuitem_type_slider
)
65 name
= mdl_pstr( &menu
.model
, item
->slider
.pstr_data
);
67 name
= mdl_pstr( &menu
.model
, item
->checkmark
.pstr_data
);
68 vg_var
*var
= vg_console_match_var( name
);
71 if( ( item
->type
== k_ent_menuitem_type_slider
&&
72 var
->data_type
!= k_var_dtype_f32
74 ( item
->type
== k_ent_menuitem_type_toggle
&&!
75 ( var
->data_type
== k_var_dtype_i32
||
76 var
->data_type
== k_var_dtype_u32
80 vg_error( "Cannot hook to data %s(%p), because it is type %d.\n",
81 name
, var
, var
->data_type
);
85 item
->pvoid
= var
->data
;
89 vg_error( "No data named %s\n", name
);
99 static void menu_init(void)
101 void *alloc
= vg_mem
.rtmemory
;
103 mdl_open( &menu
.model
, "models/rs_menu.mdl", alloc
);
104 mdl_load_metadata_block( &menu
.model
, alloc
);
106 vg_linear_clear( vg_mem
.scratch
);
108 mdl_load_array( &menu
.model
, &menu
.items
, "ent_menuitem", alloc
);
109 mdl_load_array( &menu
.model
, &menu
.markers
, "ent_marker", alloc
);
110 mdl_load_array( &menu
.model
, &menu
.cameras
, "ent_camera", alloc
);
112 vg_linear_clear( vg_mem
.scratch
);
114 if( !mdl_arrcount( &menu
.model
.textures
) )
115 vg_fatal_error( "No texture in menu file" );
117 mdl_texture
*tex0
= mdl_arritm( &menu
.model
.textures
, 0 );
118 void *data
= vg_linear_alloc( vg_mem
.scratch
, tex0
->file
.pack_size
);
119 mdl_fread_pack_file( &menu
.model
, &tex0
->file
, data
);
121 mdl_async_load_glmesh( &menu
.model
, &menu
.mesh
);
122 vg_tex2d_load_qoi_async( data
, tex0
->file
.pack_size
,
123 VG_TEX2D_LINEAR
|VG_TEX2D_CLAMP
,
126 mdl_close( &menu
.model
);
127 shader_model_menu_register();
131 * Drop back a page until we're at the bottom which then we jus quit
133 static void menu_back_page(void)
135 vg_info( "menu_back_page()\n" );
137 if( menu
.page_depth
== 0 ){
139 menu
.page
= 0xffffffff;
142 menu
.page
= menu
.page_stack
[ menu
.page_depth
].page
;
143 menu
.cam
= menu
.page_stack
[ menu
.page_depth
].cam
;
145 if( menu
.input_mode
== k_menu_input_mode_keys
)
146 menu
.loc
= menu
.page_stack
[ menu
.page_depth
].loc
;
147 else menu
.loc
= NULL
;
152 * Open page to the string identifier
154 static void menu_open_page( const char *name
)
156 if( menu
.page_depth
>= MENU_STACK_SIZE
)
157 vg_fatal_error( "Stack overflow\n" );
159 vg_info( "Try to open %s\n", name
);
161 u32 hash
= vg_strdjb2( name
);
162 for( u32 i
=0; i
<mdl_arrcount(&menu
.items
); i
++ ){
163 ent_menuitem
*item
= mdl_arritm( &menu
.items
, i
);
165 if( item
->type
== k_ent_menuitem_type_page
){
166 if( mdl_pstreq( &menu
.model
, item
->page
.pstr_name
, name
, hash
) ){
167 u32 new_page
= __builtin_ctz( item
->groups
);
169 if( new_page
== menu
.page
){
173 menu
.page_stack
[ menu
.page_depth
].page
= menu
.page
;
174 menu
.page_stack
[ menu
.page_depth
].cam
= menu
.cam
;
175 menu
.page_stack
[ menu
.page_depth
++ ].loc
= menu
.loc
;
176 menu
.page
= __builtin_ctz( item
->groups
);
178 if( menu
.input_mode
== k_menu_input_mode_keys
){
179 if( item
->page
.id_entrypoint
){
180 u32 id
= mdl_entity_id_id( item
->page
.id_entrypoint
);
181 menu
.loc
= mdl_arritm( &menu
.items
, id
);
185 if( item
->page
.id_viewpoint
){
186 u32 id
= mdl_entity_id_id( item
->page
.id_viewpoint
);
187 menu
.cam
= mdl_arritm( &menu
.cameras
, id
);
189 vg_info( "menu page: %u (%p,%p)\n",
190 menu
.page
, menu
.loc
, menu
.cam
);
199 * activate a pressable type
201 static void menu_trigger_item( ent_menuitem
*item
)
203 if ( item
->type
== k_ent_menuitem_type_event_button
){
204 u32 q
= item
->button
.pstr
;
206 if( MDL_CONST_PSTREQ( &menu
.model
, q
, "quit" ) ){
207 vg
.window_should_close
= 1;
209 else if( MDL_CONST_PSTREQ( &menu
.model
, q
, "reset_nearest" ) ){
210 localplayer_cmd_respawn( 0, NULL
);
214 menu
.page
= 0xffffffff;
216 else if( MDL_CONST_PSTREQ( &menu
.model
, q
, "reset_home" ) ){
217 world_static
.active_world
= 0;
218 world_static
.active_trigger_volume_count
= 0;
219 localplayer
.viewable_world
= world_current_instance();
220 localplayer_cmd_respawn( 1, (const char *[]){"start"} );
224 menu
.page
= 0xffffffff;
226 else if( MDL_CONST_PSTREQ( &menu
.model
, q
, "credits" ) ){
227 menu
.credits_open
= 1;
229 else if( MDL_CONST_PSTREQ( &menu
.model
, q
, "workshop" ) ){
230 workshop_submit_command(0,NULL
);
233 else if( item
->type
== k_ent_menuitem_type_page_button
){
234 menu_open_page( mdl_pstr( &menu
.model
, item
->button
.pstr
) );
236 else if( item
->type
== k_ent_menuitem_type_toggle
){
238 *item
->pi32
= *item
->pi32
^ 0x1;
243 static f32
menu_slider_snap( f32 value
, f32 old
, f32 notch
)
245 f32
const k_epsilon
= 0.0125f
;
247 if( fabsf(notch
-value
) < k_epsilon
){
248 if( fabsf(notch
-old
) > k_epsilon
){
250 audio_oneshot( &audio_ui
[0], 1.0f
, 0.0f
);
261 * Run from vg_gui every frame
263 static void menu_update(void)
265 if( workshop_form
.page
!= k_workshop_form_hidden
){
269 int escape
= button_down( k_srbind_mback
);
270 if( menu
.credits_open
){
272 menu
.credits_open
= 0;
277 if( button_down( k_srbind_mopen
) ){
278 if( !menu
.active
&& !menu
.disable_open
){
280 menu
.page
= 0xffffffff;
281 menu_open_page( "Main Menu" );
286 menu
.factive
= vg_lerpf( menu
.factive
, menu
.active
,
287 vg
.time_frame_delta
* 6.0f
);
289 if( !menu
.active
) return;
291 enum menu_input_mode prev_mode
= menu
.input_mode
;
293 /* get buttons inputs
294 * -------------------------------------------------------------------*/
295 int ml
= button_down( k_srbind_mleft
),
296 mr
= button_down( k_srbind_mright
),
297 mu
= button_down( k_srbind_mup
),
298 md
= button_down( k_srbind_mdown
),
301 enter
= button_down( k_srbind_maccept
);
304 menu
.input_mode
= k_menu_input_mode_keys
;
308 * --------------------------------------------------------------------*/
309 menu
.mouse_dist
+= v2_length( vg
.mouse_delta
); /* TODO: Move to UI */
310 menu
.mouse_track
+= vg
.time_frame_delta
;
311 if( menu
.mouse_track
> 0.1f
){
312 menu
.mouse_track
= fmodf( menu
.mouse_track
, 0.1f
);
313 if( menu
.mouse_dist
> 10.0f
){
314 menu
.input_mode
= k_menu_input_mode_mouse
;
315 menu
.mouse_dist
= 0.0f
;
319 if( ui_clicking(UI_MOUSE_LEFT
) || ui_clicking(UI_MOUSE_RIGHT
) ){
320 menu
.input_mode
= k_menu_input_mode_mouse
;
323 if( menu
.input_mode
== k_menu_input_mode_mouse
){
326 * ------------------------------------------------------------*/
327 vg_ui
.wants_mouse
= 1;
330 * this raycasting is super cumbersome because all the functions were
331 * designed for other purposes. we dont care though.
334 m4x4_inv( menu
.view
.mtx
.p
, inverse
);
336 coords
[0] = vg_ui
.mouse
[0];
337 coords
[1] = vg
.window_y
- vg_ui
.mouse
[1];
338 v2_div( coords
, (v2f
){ vg
.window_x
, vg
.window_y
}, coords
);
339 v2_muls( coords
, 2.0f
, coords
);
340 v2_add( coords
, (v2f
){-1.0f
,-1.0f
}, coords
);
343 m4x4_mulv( inverse
, coords
, coords
);
345 m3x3_mulv( menu
.view
.transform
, coords
, ray
);
348 if( menu
.loc
&& (menu
.loc
->type
== k_ent_menuitem_type_slider
) &&
349 ui_clicking(UI_MOUSE_LEFT
) && menu
.loc
->pf32
){
351 u32 il
= mdl_entity_id_id( menu
.loc
->slider
.id_min
),
352 ir
= mdl_entity_id_id( menu
.loc
->slider
.id_max
);
353 ent_marker
*ml
= mdl_arritm( &menu
.markers
, il
),
354 *mr
= mdl_arritm( &menu
.markers
, ir
);
357 v3_muladds( menu
.view
.pos
, ray
, 100.0f
, q2
);
362 v3_sub( mr
->transform
.co
, ml
->transform
.co
, v0
);
363 v3_muladds( ml
->transform
.co
, v0
, -1.0f
, p1
);
364 v3_muladds( mr
->transform
.co
, v0
, 1.0f
, q1
);
365 closest_segment_segment( p1
, q1
, menu
.view
.pos
, q2
, &s
,&t
, c1
,c2
);
370 if( ui_click_down(UI_MOUSE_LEFT
) ){
371 menu
.slider_offset
= *menu
.loc
->pf32
- s
;
374 f32 newvalue
= vg_clampf( s
+menu
.slider_offset
, 0.0f
, 1.0f
);
376 newvalue
= menu_slider_snap( newvalue
, *menu
.loc
->pf32
, 0.00f
);
377 newvalue
= menu_slider_snap( newvalue
, *menu
.loc
->pf32
, 1.00f
);
378 newvalue
= menu_slider_snap( newvalue
, *menu
.loc
->pf32
, 0.25f
);
379 newvalue
= menu_slider_snap( newvalue
, *menu
.loc
->pf32
, 0.50f
);
380 newvalue
= menu_slider_snap( newvalue
, *menu
.loc
->pf32
, 0.75f
);
382 *menu
.loc
->pf32
= newvalue
;
386 ent_menuitem
*hit_item
= NULL
;
388 for( u32 i
=0; i
<mdl_arrcount(&menu
.items
); i
++ ){
389 ent_menuitem
*item
= mdl_arritm( &menu
.items
, i
);
391 if( item
->type
== k_ent_menuitem_type_page
) continue;
392 if( item
->type
== k_ent_menuitem_type_visual
) continue;
393 if( !(item
->groups
& (0x1<<menu
.page
)) ) continue;
395 ent_menuitem
*ray_item
= item
;
397 if( item
->type
== k_ent_menuitem_type_slider
){
398 u32 subtarget
= mdl_entity_id_id( item
->slider
.id_handle
);
399 ray_item
= mdl_arritm( &menu
.items
, subtarget
);
406 mdl_transform_m4x3( &ray_item
->transform
, inverse_mtx
);
407 m4x3_invert_full( inverse_mtx
, inverse_mtx
);
409 m4x3_mulv( inverse_mtx
, menu
.view
.transform
[3], local_co
);
410 m3x3_mulv( inverse_mtx
, ray
, local_ray
);
411 v3_normalize( local_ray
);
413 local_ray
[0] = 1.0f
/local_ray
[0];
414 local_ray
[1] = 1.0f
/local_ray
[1];
415 local_ray
[2] = 1.0f
/local_ray
[2];
417 for( u32 j
=0; j
<ray_item
->submesh_count
; j
++ ){
418 mdl_submesh
*sm
= mdl_arritm( &menu
.model
.submeshs
,
419 ray_item
->submesh_start
+ j
);
420 if( ray_aabb1( sm
->bbx
, local_co
, local_ray
, 1000.0f
) ){
427 if( hit_item
!= menu
.loc
){
435 if( ui_click_down( UI_MOUSE_LEFT
) ){
436 menu_trigger_item( menu
.loc
);
440 else if( menu
.input_mode
== k_menu_input_mode_keys
){
442 * handle button input
443 * ------------------------------------------------------------*/
444 if( (prev_mode
!= k_menu_input_mode_keys
) && !menu
.loc
){
445 for( u32 i
=0; i
<mdl_arrcount(&menu
.items
); i
++ ){
446 ent_menuitem
*item
= mdl_arritm( &menu
.items
, i
);
448 if( (item
->type
!= k_ent_menuitem_type_page
) &&
449 (item
->type
!= k_ent_menuitem_type_visual
) &&
450 (item
->groups
& (0x1<<menu
.page
)) ){
456 if( !menu
.loc
) vg_fatal_error( "No location\n" );
458 if( menu
.loc
->type
== k_ent_menuitem_type_slider
&& menu
.loc
->pf32
){
461 if( vg_input
.display_input_method
== k_input_method_controller
){
462 move
+= button_press( k_srbind_mright
);
463 move
-= button_press( k_srbind_mleft
);
466 move
+= axis_state( k_sraxis_mbrowse_h
);
469 move
*= vg
.time_frame_delta
;
470 *menu
.loc
->pf32
= vg_clampf( *menu
.loc
->pf32
+ move
, 0.0f
, 1.0f
);
479 menu_trigger_item( menu
.loc
);
485 ent_menuitem
*nextpos
= NULL
;
489 mdl_transform_vector( &menu
.cam
->transform
, opt
, opt
);
491 for( u32 i
=0; i
<4; i
++ ){
492 u32 id
= menu
.loc
->id_links
[i
];
494 u32 index
= mdl_entity_id_id( id
);
496 ent_menuitem
*other
= mdl_arritm( &menu
.items
, index
);
498 v3_sub( menu
.loc
->transform
.co
, other
->transform
.co
, delta
);
499 v3_normalize( delta
);
501 f32 score
= v3_dot( delta
, opt
);
516 * Run from vg_gui when active
518 VG_STATIC
void menu_render(void)
521 glDisable(GL_DEPTH_TEST
);
522 glBlendFunc( GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
523 glBlendEquation(GL_FUNC_ADD
);
525 shader_blitcolour_use();
527 ui_hex_to_norm( ui_colour( k_ui_bg
+3 ), colour
);
530 shader_blitcolour_uColour( colour
);
533 if( (workshop_form
.page
!= k_workshop_form_hidden
) ||
534 (vg_ui
.focused_control_type
!= k_ui_control_none
) ){
538 if( menu
.credits_open
){
539 ui_rect panel
= { 0,0, 460, 400 },
540 screen
= { 0,0, vg
.window_x
,vg
.window_y
};
541 ui_rect_center( screen
, panel
);
542 ui_fill( panel
, ui_colour(k_ui_bg
) );
543 ui_outline( panel
, 1, ui_colour(k_ui_fg
) );
544 ui_rect_pad( panel
, (ui_px
[]){8,8} );
547 ui_split( panel
, k_ui_axis_h
, 28*2, 0, title
, panel
);
548 ui_text( title
, "Skate Rift - Credits", 2, k_ui_align_middle_center
, 0 );
549 ui_split( panel
, k_ui_axis_h
, 28, 0, title
, panel
);
550 ui_text( title
, "Mt.Zero Software", 1, k_ui_align_middle_center
, 0 );
552 ui_split( panel
, k_ui_axis_h
, 8, 0, title
, panel
);
553 ui_split( panel
, k_ui_axis_h
, 28, 0, title
, panel
);
554 ui_text( title
, "A game by Harry Godden", 1,
555 k_ui_align_middle_center
, 0 );
557 ui_split( panel
, k_ui_axis_h
, 8, 0, title
, panel
);
558 ui_split( panel
, k_ui_axis_h
, 28*2, 0, title
, panel
);
559 ui_text( title
, "Free Software", 2, k_ui_align_middle_center
, 0 );
561 ui_split( panel
, k_ui_axis_h
, 8, 0, title
, panel
);
563 "Sam Lantinga - SDL2 - libsdl.org\n"
564 "Hunter WB - Anyascii\n"
565 "David Herberth - GLAD\n"
566 "Dominic Szablewski - QOI - qoiformat.org\n"
567 "Sean Barrett - stb_image,stb_vorbis,stb_include\n"
568 "Khronos Group - OpenGL\n"
569 , 1, k_ui_align_left
, 0 );
573 glEnable( GL_DEPTH_TEST
);
574 glDisable( GL_BLEND
);
576 f32 rate
= vg
.time_frame_delta
* 12.0f
;
581 target
.fov
= menu
.cam
->fov
;
582 v3_copy( menu
.cam
->transform
.co
, target
.pos
);
585 mdl_transform_vector( &menu
.cam
->transform
, (v3f
){0.0f
,-1.0f
,0.0f
}, v0
);
586 player_vector_angles( target
.angles
, v0
, 1.0f
, 0.0f
);
588 camera_lerp( &menu
.view
, &target
, rate
, &menu
.view
);
590 menu
.view
.farz
= 150.0f
;
591 menu
.view
.nearz
= 0.01f
;
593 camera_update_transform( &menu
.view
);
594 camera_update_view( &menu
.view
);
595 camera_update_projection( &menu
.view
);
596 camera_finalize( &menu
.view
);
600 shader_model_menu_use();
601 shader_model_menu_uTexMain( 1 );
602 glActiveTexture( GL_TEXTURE1
);
603 glBindTexture( GL_TEXTURE_2D
, menu
.texture
);
604 shader_model_menu_uPv( menu
.view
.mtx
.pv
);
605 shader_model_menu_uPvmPrev( menu
.view
.mtx_prev
.pv
);
607 mesh_bind( &menu
.mesh
);
611 ui_hex_to_norm( ui_colour( k_ui_fg
), white
);
612 ui_hex_to_norm( ui_colour( k_ui_orange
+k_ui_brighter
), blue
);
614 for( u32 i
=0; i
<mdl_arrcount(&menu
.items
); i
++ ){
615 ent_menuitem
*item
= mdl_arritm( &menu
.items
, i
);
617 if( item
->type
== k_ent_menuitem_type_page
) continue;
618 if( !(item
->groups
& (0x1 << menu
.page
)) ) continue;
623 if( menu
.loc
->type
== k_ent_menuitem_type_slider
){
624 u32 subid
= menu
.loc
->slider
.id_handle
;
625 if( item
== mdl_arritm( &menu
.items
, mdl_entity_id_id(subid
) ))
629 if( item
== menu
.loc
)
634 item
->factive
= vg_lerpf( item
->factive
, selected
, rate
);
636 v4_lerp( white
, blue
, item
->factive
, colour
);
637 shader_model_menu_uColour( colour
);
639 f32 scale
= 1.0f
+item
->factive
*0.1f
;
642 mdl_transform transform
= item
->transform
;
643 v3_muls( transform
.s
, scale
, transform
.s
);
644 mdl_transform_m4x3( &transform
, mmdl
);
646 if( item
->type
== k_ent_menuitem_type_toggle
&& item
->pi32
){
647 u32 subid
= mdl_entity_id_id( item
->checkmark
.id_check
);
648 ent_menuitem
*subitem
= mdl_arritm( &menu
.items
, subid
);
650 v3_muladds( item
->transform
.co
, item
->checkmark
.offset
, scale
,
651 subitem
->transform
.co
);
653 subitem
->fvisible
= vg_lerpf( subitem
->fvisible
, *item
->pi32
, rate
);
654 v3_fill( subitem
->transform
.s
, subitem
->fvisible
);
656 else if( item
->type
== k_ent_menuitem_type_slider
&& item
->pf32
){
657 u32 il
= mdl_entity_id_id( item
->slider
.id_min
),
658 ir
= mdl_entity_id_id( item
->slider
.id_max
),
659 ih
= mdl_entity_id_id( item
->slider
.id_handle
);
660 ent_marker
*ml
= mdl_arritm( &menu
.markers
, il
),
661 *mr
= mdl_arritm( &menu
.markers
, ir
);
662 ent_menuitem
*handle
= mdl_arritm( &menu
.items
, ih
);
664 v3_lerp( ml
->transform
.co
, mr
->transform
.co
, *item
->pf32
,
665 handle
->transform
.co
);
668 shader_model_menu_uMdl( mmdl
);
670 for( u32 j
=0; j
<item
->submesh_count
; j
++ ){
671 u32 index
= item
->submesh_start
+ j
;
672 mdl_draw_submesh( mdl_arritm( &menu
.model
.submeshs
, index
));