From: hgn Date: Thu, 22 Feb 2024 08:42:39 +0000 (+0000) Subject: build system revision X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;ds=sidebyside;h=3b14f3dcd5bf9dd3c85144f2123d667bfa4bb63f;p=vg.git build system revision --- diff --git a/labs/build.c b/labs/build.c index bb0b343..973f420 100644 --- a/labs/build.c +++ b/labs/build.c @@ -3,17 +3,15 @@ void s_lab_physics(void){ vg_info( "running script: s_lab_physics(void)\n" ); - vg_build.fresh = 0; - vg_build.platform = k_platform_linux; - vg_build.arch = k_architecture_x86_64; - vg_build.compiler = k_compiler_clang; - vg_build.libc = k_libc_version_native; - vg_build_new( "physics" ); - vg_add_source( "physics.c" ); - vg_add_graphics(); - vg_add_game_stuff(); - vg_compile( "physics" ); + struct vg_project project; + struct vg_env env = vg_test_env; + + vg_project_init( &project, &vg_test_env, "labs" ); + vg_project_new_target( &project, "physics", k_obj_type_exe ); + vg_add_engine( &project, NULL ); + vg_add_source( &project, "physics.c" ); + vg_compile_project( &project ); vg_success( "Completed 1/1\n" ); } @@ -24,11 +22,8 @@ int main( int argc, char *argv[] ){ s_lab_physics(); if( vg_opt('r') ) - vg_build.optimization = 3; + vg_test_env.optimization = 3; } - if( vg_build.warnings ) - vg_warn( "Finished with %u warnings\n", vg_build.warnings ); - else - vg_success( "All scripts ran successfully\n" ); + vg_success( "All scripts ran successfully\n" ); } diff --git a/labs/build.sh b/labs/build.sh index d807648..f5175ea 100755 --- a/labs/build.sh +++ b/labs/build.sh @@ -1 +1 @@ -clang -fsanitize=address -O0 -I./vg build.c -o /tmp/tmpsr && /tmp/tmpsr $@ +clang -fsanitize=address -O0 -I./vg build.c vg/vg_tool.c -o /tmp/tmpsr && /tmp/tmpsr $@ diff --git a/labs/physics.c b/labs/physics.c index 7373660..c570a31 100644 --- a/labs/physics.c +++ b/labs/physics.c @@ -1,23 +1,13 @@ -#define VG_GAME -#define VG_AUDIO_FORCE_COMPRESSED -#define VG_3D -#define VG_LOG_SOURCE_INFO -#define VG_TIMESTEP_FIXED (1.0/60.0) - -#ifndef VG_RELEASE - #define VG_DEVWINDOW -#endif - -#define SDL_MAIN_HANDLED - -#define VG_MAX_CONTACTS 2048 - -#include "vg/vg.h" +#include "vg/vg_engine.h" +#include "vg/vg_opt.h" +#include "vg/vg_audio.h" #include "vg/vg_camera.h" #include "vg/vg_rigidbody.h" #include "vg/vg_rigidbody_collision.h" +#include "vg/vg_rigidbody_view.h" #include "vg/vg_profiler.h" #include "vg/vg_bvh.h" +#include "vg/vg_input.h" #define SHAPE_MAX 256 static rigidbody shapes[SHAPE_MAX]; @@ -105,15 +95,17 @@ int main( int argc, char *argv[] ){ return 0; } -static void vg_launch_opt(void){ +void vg_launch_opt(void){ const char *arg; } -static void vg_preload(void){ +void vg_preload(void) +{ vg_audio.dsp_enabled = 0; } -static void init_random(void){ +static void init_random(void) +{ for( u32 i=0; i<(u32)k_shapes; i ++ ){ f32 h = vg_randf64( &vg.rand ) * 2.0f + 1.3f, r = vg_randf64( &vg.rand ) * 0.5f + 0.125f, @@ -181,7 +173,8 @@ static void reset_racket(void){ rb_update_matrices( &racket ); } -static void vg_load(void){ +void vg_load(void) +{ vg_bake_shaders(); init_random(); shape_bvh_tree = bh_create( NULL, &shape_bvh, NULL, SHAPE_MAX, 1 ); @@ -189,7 +182,8 @@ static void vg_load(void){ reset_racket(); } -static void vg_pre_update(void){ +void vg_pre_update(void) +{ vg_console.cheats = 1; vg_lines.render = 1; } @@ -343,7 +337,8 @@ static void demo0(void){ vg_profile_increment( &prof_narrow ); vg_profile_begin( &prof_solve ); - rb_presolve_contacts( rb_contact_buffer, rb_contact_count ); + rb_presolve_contacts( rb_contact_buffer, + vg.time_fixed_delta, rb_contact_count ); for( u32 i=0; i<(u32)k_iterations; i ++ ) rb_solve_contacts( rb_contact_buffer, rb_contact_count ); @@ -377,17 +372,20 @@ static void demo1(void){ vg_profile_end( &prof_solve ); } -static void vg_fixed_update(void){ +void vg_fixed_update(void) +{ if( k_demo == 0 ) demo0(); else if( k_demo == 1 ) demo1(); } -static void vg_post_update(void){ +void vg_post_update(void) +{ if( vg_getkey( SDLK_8 ) ) init_random(); } -static void vg_framebuffer_resize( int w, int h ){ +void vg_framebuffer_resize( int w, int h ) +{ } static void draw_origin_axis(void){ @@ -444,7 +442,8 @@ static void render1(void){ vg_rb_view_capsule( mmdl, racket_cb.r, racket_cb.h, (v4f){0,1,0,1} ); } -static void vg_render(void){ +void vg_render(void) +{ glBindFramebuffer( GL_FRAMEBUFFER, 0 ); glViewport( 0,0, vg.window_x, vg.window_y ); glEnable( GL_DEPTH_TEST ); @@ -537,7 +536,8 @@ static void gui1( ui_rect panel ){ } } -static void vg_gui(void){ +void vg_gui(void) +{ vg_ui.wants_mouse = 1; ui_rect panel = { vg.window_x-300, 0, 300, vg.window_y }; ui_rect_pad( panel, (ui_px[2]){ 8, 8 } ); diff --git a/vg.h b/vg.h index 119e9b1..2d06aab 100644 --- a/vg.h +++ b/vg.h @@ -1,955 +1,86 @@ -/* Copyright (C) 2021-2024 Mt.Zero Software - All Rights Reserved */ - -/* - - .-. VG Event loop -| 0 | -| | .---------------------------------------------------------. -|API| | vg_enter( int argc, char *argv[], const char *window_name | -| | '---------------------------------------------------------' -| | | -| | v -|IMP| vg_launch_opt(void) <--. -| | | | -| | |'---------------' -| | | .-. -| | |'-----------------------------------| 1 |------. -| | | | | | -| | | | | v -| | | |IMP| vg_preload(void) -| | | | | | -| | .-----+. | | v -| | | | |IMP| vg_load(void) -| | | v '___' | -|IMP| | vg_framebuffer_resize(void) | -| | | | | -|IMP| | |.------------- vg_start(void) ---------------' -| | | | -| | | v -|IMP| | vg_pre_update(void) -| | | | -| | | .-----+. -| | | | | called 0x to 8x -| | | | v -|IMP| | '- vg_fixed_update(void) -| | | | -| | | .-' -| | | | -| | | v -|IMP| | vg_post_update(void) -| | | | -| | | v -|IMP| | vg_render(void) -| | | | -| | | v -|IMP| | vg_gui(void) -| | | | -| | | v -|IMP| | vg_game_settings_init(void) -|IMP| | vg_game_settings_gui( ui_rect panel ) -| | | | (optional: #define VG_GAME_SETTINGS) -| | | | -| | '----' -'___' - -*/ - -#ifndef VG_HEADER_H - #define VG_HEADER_H - - const char *vg_get_basepath(void); - - #include "vg_platform.h" - #include "vg_mem.h" - - #ifdef VG_GAME - #include "dep/glad/glad.h" - #include "dep/sdl/include/SDL.h" - #include "vg_stdint.h" - - void vg_register_exit( void( *funcptr )(void), const char *name ); - - #include "vg_m.h" - #include "vg_io.h" - #include "vg_log.h" -#ifndef VG_NO_STEAM - #include "vg_steam.h" -#endif - - //#define VG_SYNC_DEBUG - #ifdef VG_SYNC_DEBUG - #define VG_SYNC_LOG(STR,...) \ - vg_info(STR,SDL_GetThreadID(NULL),##__VA_ARGS__) - #else - #define VG_SYNC_LOG(...) - #endif - -/* API */ -static void vg_enter( int argc, char *argv[], const char *window_name ); - -/* Thread 1 */ -static void vg_preload(void); -static void vg_load(void); - -/* Main thread */ -static void vg_launch_opt(void); -static void vg_start(void); - -static void vg_framebuffer_resize(int w, int h); -static void vg_pre_update(void); -static void vg_fixed_update(void); -static void vg_post_update(void); - -static void vg_render(void); -static void vg_gui(void); - -enum quality_profile{ - k_quality_profile_high = 0, - k_quality_profile_low = 1, - k_quality_profile_min = 2 -}; - -struct vg{ - /* Engine sync */ - SDL_Window *window; - SDL_GLContext gl_context; - const char *base_path; - - SDL_sem *sem_loader; /* allows only one loader at a time */ - jmp_buf env_loader_exit; - - SDL_threadID thread_id_main, - thread_id_loader; - void *thread_data; - - SDL_SpinLock sl_status; - enum engine_status{ - k_engine_status_none, - k_engine_status_load_internal, - k_engine_status_running, - k_engine_status_crashed - } - engine_status; - - /* Window information */ - int window_x, - window_y, - samples, - window_should_close; - - int display_refresh_rate, - fps_limit, - vsync, - screen_mode, - display_index; - - int settings_open; - - enum vsync_feature{ - k_vsync_feature_disabled=0, - k_vsync_feature_enabled=1, - k_vsync_feature_enabled_adaptive=2, - k_vsync_feature_error=3 - } - vsync_feature; - - i32 mouse_pos[2]; - v2f mouse_delta, - mouse_wheel; - - /* Runtime */ - double time, - time_real, - time_delta, - time_rate, - - time_fixed_accumulator, - time_fixed_extrapolate, - time_frame_delta; - - f32 time_fixed_delta; - - u64 time_hp, time_hp_last, time_spinning; - - int fixed_iterations; - - enum engine_stage{ - k_engine_stage_none, - k_engine_stage_update, - k_engine_stage_update_fixed, - k_engine_stage_rendering, - k_engine_stage_ui - } - engine_stage; - - /* graphics */ -#ifdef VG_3D - m4x4f pv; -#else - m3x3f pv; -#endif - - i32 quality_profile; - - float loader_ring; - GLuint tex_missing; - - vg_rand rand; -} -static vg = { .time_rate = 1.0, .time_fixed_delta = VG_TIMESTEP_FIXED }; -const char *vg_get_basepath(void){ - return vg.base_path; -} - -enum vg_thread_purpose -{ - k_thread_purpose_nothing, - k_thread_purpose_main, - k_thread_purpose_loader -}; - -#include "vg_async.h" - -static enum engine_status _vg_engine_status(void) -{ - SDL_AtomicLock( &vg.sl_status ); - enum engine_status status = vg.engine_status; - SDL_AtomicUnlock( &vg.sl_status ); - - return status; -} - -static enum vg_thread_purpose vg_thread_purpose(void) -{ - SDL_AtomicLock( &vg.sl_status ); - - if( vg.thread_id_main == SDL_GetThreadID(NULL) ){ - SDL_AtomicUnlock( &vg.sl_status ); - return k_thread_purpose_main; - } - else{ - SDL_AtomicUnlock( &vg.sl_status ); - return k_thread_purpose_loader; - } -} - -static void vg_assert_thread( enum vg_thread_purpose required ){ - enum vg_thread_purpose purpose = vg_thread_purpose(); - - if( purpose != required ){ - vg_fatal_error( "thread_purpose must be %u not %u\n", required, purpose ); - } -} - -static void _vg_opengl_sync_init(void) -{ - vg.sem_loader = SDL_CreateSemaphore(1); -} - -static void vg_checkgl( const char *src_info ); -#define VG_STRINGIT( X ) #X -#define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) ) - -#include "vg_console.h" -#include "vg_profiler.h" -#ifndef VG_NO_AUDIO - #include "vg_audio.h" -#endif -#include "vg_shader.h" -#include "vg_tex.h" -#include "vg_input.h" -#include "vg_imgui.h" -#include "vg_lines.h" -#include "vg_rigidbody_view.h" -#include "vg_loader.h" -#include "vg_opt.h" -#include "vg_settings_menu.h" - -/* Diagnostic */ -static struct vg_profile vg_prof_update = {.name="update()"}, - vg_prof_render = {.name="render()"}, - vg_prof_swap = {.name="swap"}; - -static void vg_checkgl( const char *src_info ) -{ - int fail = 0; - - GLenum err; - while( (err = glGetError()) != GL_NO_ERROR ){ - vg_error( "(%s) OpenGL Error: #%d\n", src_info, err ); - fail = 1; - } - - if( fail ) - vg_fatal_error( "OpenGL Error" ); -} - -static void async_vg_bake_shaders( void *payload, u32 size ) -{ - vg_shaders_compile(); -} - -static void vg_bake_shaders(void) -{ - vg_console_reg_cmd( "reload_shaders", vg_shaders_live_recompile, NULL ); - vg_async_call( async_vg_bake_shaders, NULL, 0 ); -} - -void async_internal_complete( void *payload, u32 size ) -{ - vg_success( "Internal async setup complete\n" ); - SDL_AtomicLock( &vg.sl_status ); - - if( vg.engine_status == k_engine_status_crashed ){ - SDL_AtomicUnlock( &vg.sl_status ); - return; - } - else{ - vg.engine_status = k_engine_status_running; - } - - SDL_AtomicUnlock( &vg.sl_status ); -} - -static void _vg_load_full( void *data ){ - vg_preload(); - vg_tex2d_replace_with_error( &vg.tex_missing ); - vg_ui.tex_bg = vg.tex_missing; - - /* internal */ - vg_loader_step( vg_input_init, vg_input_free ); - vg_loader_step( vg_lines_init, NULL ); - vg_loader_step( vg_rb_view_init, NULL ); -#ifndef VG_NO_AUDIO - vg_loader_step( vg_audio_init, vg_audio_free ); -#endif - vg_loader_step( vg_profiler_init, NULL ); - - /* client */ - vg_load(); - - vg_async_call( async_internal_complete, NULL, 0 ); - - - vg_success( "Client loaded in %fs\n", vg.time_real ); -} - -static void _vg_process_events(void) -{ - v2_zero( vg.mouse_wheel ); - v2_zero( vg.mouse_delta ); - - /* Update input */ - vg_process_inputs(); - - /* SDL event loop */ - SDL_Event event; - while( SDL_PollEvent( &event ) ){ - if( event.type == SDL_KEYDOWN ){ - if( vg_console.enabled && - (vg_ui.focused_control_type != k_ui_control_modal) ){ - if( event.key.keysym.sym == SDLK_ESCAPE || - event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ - vg_console.enabled = 0; - ui_defocus_all(); - } - else if( (event.key.keysym.mod & KMOD_CTRL) && - event.key.keysym.sym == SDLK_n ){ - _console_suggest_next(); - } - else if( (event.key.keysym.mod & KMOD_CTRL ) && - event.key.keysym.sym == SDLK_p ){ - _console_suggest_prev(); - } - else{ - _ui_proc_key( event.key.keysym ); - } - } - else{ - if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ - vg_console.enabled = 1; - } - else { - _ui_proc_key( event.key.keysym ); - } - } - } - else if( event.type == SDL_MOUSEWHEEL ){ - vg.mouse_wheel[0] += event.wheel.preciseX; - vg.mouse_wheel[1] += event.wheel.preciseY; - } - else if( event.type == SDL_CONTROLLERDEVICEADDED || - event.type == SDL_CONTROLLERDEVICEREMOVED ) - { - vg_input_device_event( &event ); - } - else if( event.type == SDL_CONTROLLERAXISMOTION || - event.type == SDL_CONTROLLERBUTTONDOWN || - event.type == SDL_CONTROLLERBUTTONUP ) - { - vg_input_controller_event( &event ); - } - else if( event.type == SDL_MOUSEMOTION ){ - vg.mouse_delta[0] += event.motion.xrel; - vg.mouse_delta[1] += event.motion.yrel; - } - else if( event.type == SDL_WINDOWEVENT ){ - if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ){ - int w, h; - SDL_GL_GetDrawableSize( vg.window, &w, &h ); - - if( !w || !h ){ - vg_warn( "Got a invalid framebuffer size: " - "%dx%d... ignoring\n", w, h ); - } - else{ - vg.window_x = w; - vg.window_y = h; - - vg_framebuffer_resize(w,h); - } - } - else if( event.window.event == SDL_WINDOWEVENT_CLOSE ){ - vg.window_should_close = 1; - } - } - else if( event.type == SDL_TEXTINPUT ){ - ui_proc_utf8( event.text.text ); - } - } - - SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] ); -} - -static void _vg_gameloop_update(void) -{ - vg_profile_begin( &vg_prof_update ); - - vg.engine_stage = k_engine_stage_update; - vg_pre_update(); - - /* Fixed update loop */ - vg.engine_stage = k_engine_stage_update_fixed; - - vg.fixed_iterations = 0; - vg_lines.enabled = vg_lines.render; - vg.time_fixed_accumulator += vg.time_delta; - - while( vg.time_fixed_accumulator >= vg.time_fixed_delta ){ - vg_fixed_update(); - vg_lines.enabled = 0; - vg.time_fixed_accumulator -= vg.time_fixed_delta; - - vg.fixed_iterations ++; - if( vg.fixed_iterations == 8 ){ - break; - } - } - vg_lines.enabled = vg_lines.render; - vg.time_fixed_extrapolate = vg.time_fixed_accumulator / vg.time_fixed_delta; - - vg.engine_stage = k_engine_stage_update; - vg_post_update(); - vg_profile_end( &vg_prof_update ); -} - -static void _vg_gameloop_render(void) -{ - vg_profile_begin( &vg_prof_render ); - - /* render */ - vg.engine_stage = k_engine_stage_rendering; - vg_render(); - - vg_profile_end( &vg_prof_render ); - - /* ui */ - vg.engine_stage = k_engine_stage_ui; - { - ui_prerender(); - if( vg_console.enabled ){ - vg_ui.ignore_input_frames = 10; - vg_gui(); - vg_ui.ignore_input_frames = 0; - vg_ui.wants_mouse = 1; - _vg_console_draw(); - } - else vg_gui(); - - if( vg.settings_open ) - vg_settings_gui(); - - /* vg tools */ -#ifndef VG_NO_AUDIO - audio_debug_ui( vg.pv ); -#endif - - /* profiling */ - if( vg_profiler ){ - int frame_target = vg.display_refresh_rate; - if( vg.fps_limit > 0 ) frame_target = vg.fps_limit; - vg_profile_drawn( - (struct vg_profile *[]){ - &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3, - (1.0f/(float)frame_target)*1000.0f, - (ui_rect){ 4, 4, 250, 0 }, 0, 0 - ); - char perf[256]; - - snprintf( perf, 255, - "x: %d y: %d\n" - "refresh: %d (%.1fms)\n" - "samples: %d\n" - "iterations: %d (acc: %.3fms%%)\n" - "time: real(%.2f) delta(%.2f) rate(%.2f)\n" - " extrap(%.2f) frame(%.2f) spin( "PRINTF_U64" )\n", - vg.window_x, vg.window_y, - frame_target, (1.0f/(float)frame_target)*1000.0f, - vg.samples, - vg.fixed_iterations, - (vg.time_fixed_accumulator/VG_TIMESTEP_FIXED)*100.0f, - vg.time_real, vg.time_delta, vg.time_rate, - vg.time_fixed_extrapolate, vg.time_frame_delta, - vg.time_spinning ); - - ui_text( (ui_rect){258,4,900,900},perf,1,0,k_ui_align_left); - } - ui_postrender(); - } -} - -static void aaaaaaaaaaaaaaaaa( ui_rect r ){ - int frame_target = vg.display_refresh_rate; - if( !vg.vsync ) frame_target = vg.fps_limit; - - vg_profile_drawn( - (struct vg_profile *[]){ - &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3, - (1.0f/(f32)frame_target)*1500.0f, - r, 1, 0 - ); - - ui_fill( (ui_rect){ r[0], r[1] + (r[3]*2)/3, r[2], 1 }, ui_colour(k_ui_fg) ); -} - -static void vg_changevsync(void){ - if( vg.vsync && (vg.vsync_feature != k_vsync_feature_error) ){ - /* turn on vsync if not enabled */ - - enum vsync_feature requested = k_vsync_feature_enabled; - if( vg.vsync < 0 ) requested = k_vsync_feature_enabled_adaptive; - - if( vg.vsync_feature != requested ){ - vg_info( "Setting swap interval\n" ); - - int swap_interval = 1; - if( requested == k_vsync_feature_enabled_adaptive ) - swap_interval = -1; - - if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ){ - if( requested == k_vsync_feature_enabled ){ - vg_error( "Vsync is not supported by your system\n" ); - vg_warn( "You may be overriding it in your" - " graphics control panel.\n" ); - } - else{ - vg_error( "Adaptive Vsync is not supported by your system\n" ); - } - - vg.vsync_feature = k_vsync_feature_error; - vg.vsync = 0; - /* TODO: Make popup to notify user that this happened */ - } - else{ - vg_success( "Vsync enabled (%d)\n", requested ); - vg.vsync_feature = requested; - } - } - } - else { - if( vg.vsync_feature != k_vsync_feature_disabled ){ - SDL_GL_SetSwapInterval( 0 ); - vg.vsync_feature = k_vsync_feature_disabled; - } - } -} - -static int vg_framefilter( double dt ){ - if( vg.fps_limit < 24 ) vg.fps_limit = 24; - if( vg.fps_limit > 300 ) vg.fps_limit = 300; - - double min_frametime = 1.0/(double)vg.fps_limit; - if( vg.time_frame_delta < min_frametime ){ - /* TODO: we can use high res nanosleep on Linux here */ - double sleep_ms = (min_frametime-vg.time_frame_delta) * 1000.0; - u32 ms = (u32)floor( sleep_ms ); - - if( ms ){ - if( !vg_loader_availible() ) - SDL_Delay(1); - else - SDL_Delay(ms); - } - else{ - vg.time_spinning ++; - } - - return 1; - } - - return 0; -} - -static int _vg_crashscreen(void) -{ -#if 0 - if( vg_getkey( SDLK_ESCAPE ) ) - return 1; -#endif - - glBindFramebuffer( GL_FRAMEBUFFER, 0 ); - glEnable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA); - glBlendEquation(GL_FUNC_ADD); - - glClearColor( 0.15f + sinf(vg.time_real)*0.1f, 0.0f, 0.0f,1.0f ); - glClear( GL_COLOR_BUFFER_BIT ); - glViewport( 0,0, vg.window_x, vg.window_y ); - - _vg_render_log(); - - return 0; -} - -static void _vg_gameloop(void){ - //vg.time_fixed_accumulator = 0.75f * (1.0f/60.0f); - - vg.time_hp = SDL_GetPerformanceCounter(); - vg.time_hp_last = vg.time_hp; - - int post_start = 0; - while(1){ - vg.time_hp = SDL_GetPerformanceCounter(); - u64 udt = vg.time_hp - vg.time_hp_last; - vg.time_hp_last = vg.time_hp; - - double dt = (double)udt / (double)SDL_GetPerformanceFrequency(); - - vg.time_frame_delta += dt; - vg_run_async_checked(); - - if( vg_framefilter( dt ) ) - continue; - - vg_changevsync(); - - enum engine_status status = _vg_engine_status(); - if( status == k_engine_status_running ) - vg_profile_begin( &vg_prof_swap ); - - SDL_GL_SwapWindow( vg.window ); - - if( status == k_engine_status_running ) - vg_profile_end( &vg_prof_swap ); - - vg.time_real += vg.time_frame_delta; - vg.time_delta = vg.time_frame_delta * vg.time_rate; - vg.time += vg.time_delta; - - _vg_process_events(); - - if( vg.window_should_close ) - break; - - if( status == k_engine_status_crashed ){ - if( _vg_crashscreen() ) - break; - } - else{ - if( status == k_engine_status_running ){ - _vg_gameloop_update(); - _vg_gameloop_render(); - } - else{ - _vg_loader_render(); - } - } - - if( vg.loader_ring > 0.01f ){ - _vg_loader_render_ring( vg.loader_ring ); - vg.loader_ring -= vg.time_frame_delta * 0.5f; - } - - vg.time_frame_delta = 0.0; - vg.time_spinning = 0; - } -} - -static void _vg_process_launch_opts_internal( int argc, char *argv[] ) -{ - char *arg; - while( vg_argp( argc, argv ) ){ - if( (arg = vg_opt_arg( 'w' )) ){ - vg.window_x = atoi( arg ); - } - - if( (arg = vg_opt_arg( 'h' )) ){ - vg.window_y = atoi( arg ); - } - - if( (arg = vg_long_opt_arg( "samples" )) ){ - vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) ); - } - - if( vg_long_opt( "use-libc-malloc" ) ){ - vg_mem.use_libc_malloc = 1; - } - - if( vg_long_opt( "high-performance" ) ){ - vg.quality_profile = k_quality_profile_low; - } - - vg_launch_opt(); - } -} - -static void _vg_init_window( const char *window_name ) -{ - vg_info( "SDL_INIT\n" ); - - if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){ - vg_error( "SDL_Init failed: %s\n", SDL_GetError() ); - exit(0); - } - -#ifndef VG_NO_AUDIO - SDL_InitSubSystem( SDL_INIT_AUDIO ); -#endif - SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER ); - - char *exe_basepath = SDL_GetBasePath(); - u32 len = vg_align8( strlen(exe_basepath)+1 ); - char *dest = vg_linear_alloc( vg_mem.rtmemory, len ); - strcpy( dest, exe_basepath ); - SDL_free( exe_basepath ); - vg.base_path = dest; - - vg_info( "Basepath: %s\n", vg.base_path ); - - SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); - SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 ); - SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 ); - SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, - SDL_GL_CONTEXT_PROFILE_CORE ); - - SDL_GL_SetAttribute( SDL_GL_CONTEXT_RELEASE_BEHAVIOR, - SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH ); - - SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); - SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); - SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); - SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 ); - SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0 ); - - /* - * Get monitor information - */ - vg_info( "Getting display count\n" ); - int display_count = 0, - display_index = 0, - mode_index = 0; - - SDL_DisplayMode video_mode; - if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ){ - vg_error( "SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError() ); - SDL_Quit(); - exit(0); - } - - vg.display_refresh_rate = video_mode.refresh_rate; - vg.window_x = video_mode.w; - vg.window_y = video_mode.h; - - if( vg.screen_mode == 2 ){ - vg.window_x = 1280; - vg.window_y = 720; - } - -#ifndef _WIN32 - SDL_SetHint( "SDL_VIDEO_X11_XINERAMA", "1" ); - SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "0" ); - SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "0" ); -#endif - - u32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_GRABBED | - SDL_WINDOW_RESIZABLE; - - if( vg.screen_mode == 1 ) - flags |= SDL_WINDOW_FULLSCREEN; - else if( vg.screen_mode == 0 ) - flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; - - vg_info( "CreateWindow( %d %d %u )\n", vg.window_x, vg.window_y, flags ); - - if((vg.window = SDL_CreateWindow( window_name, 0, 0, - vg.window_x, vg.window_y, flags ))){ - if( vg.screen_mode == 2 ) - SDL_SetWindowPosition( vg.window, video_mode.w-vg.window_x, 0 ); - } - else{ - vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() ); - exit(0); - } - - SDL_RaiseWindow( vg.window ); - SDL_SetWindowMinimumSize( vg.window, 1280, 720 ); - SDL_SetWindowMaximumSize( vg.window, 4096, 4096 ); - - vg_info( "CreateContext\n" ); - - /* ????? */ - if( SDL_IsTextInputActive() ) SDL_StopTextInput(); - - /* - * OpenGL loading - */ - if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )){ - SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y ); - vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y ); - } - else{ - vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); - SDL_Quit(); - exit(0); - } - - if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) { - vg_error( "Glad Failed to initialize\n" ); - SDL_GL_DeleteContext( vg.gl_context ); - SDL_Quit(); - exit(0); - } - - const unsigned char* glver = glGetString( GL_VERSION ); - vg_success( "Load setup complete, OpenGL version: %s\n", glver ); - - SDL_GL_SetSwapInterval(0); /* disable vsync while loading */ - - SDL_DisplayMode dispmode; - if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ){ - if( dispmode.refresh_rate ){ - vg.display_refresh_rate = dispmode.refresh_rate; - } - } - - if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ){ - vg.display_refresh_rate = 60; - } - - vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate ); - if( !vg.fps_limit) vg.fps_limit = vg.display_refresh_rate; -} - -static void _vg_terminate(void) -{ - /* Shutdown */ - _vg_console_write_persistent(); - - SDL_AtomicLock( &vg.sl_status ); - vg.engine_status = k_engine_status_none; - SDL_AtomicUnlock( &vg.sl_status ); - - _vg_loader_free(); - - vg_success( "If you see this it means everything went.. \"well\".....\n" ); - - SDL_GL_DeleteContext( vg.gl_context ); - SDL_Quit(); - exit(0); -} - -static void vg_enter( int argc, char *argv[], const char *window_name ){ - vg_rand_seed( &vg.rand, 461 ); - _vg_process_launch_opts_internal( argc, argv ); - - /* Systems init */ - vg_alloc_quota(); - _vg_console_init(); - - vg_console_reg_var( "vg_fps_limit", &vg.fps_limit, - k_var_dtype_i32, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_vsync", &vg.vsync, - k_var_dtype_i32, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_quality", &vg.quality_profile, - k_var_dtype_i32, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_screen_mode", &vg.screen_mode, - k_var_dtype_i32, VG_VAR_PERSISTENT ); - vg_audio_register(); - vg_console_load_autos(); - - vg_console_reg_cmd( "vg_settings", cmd_vg_settings_toggle, NULL ); - _vg_init_window( window_name ); - - vg_async_init(); - SDL_SetRelativeMouseMode(1); - - vg.thread_id_main = SDL_GetThreadID(NULL); - - /* Opengl-required systems */ - _vg_ui_init(); - _vg_loader_init(); - - vg.engine_status = k_engine_status_load_internal; - - _vg_opengl_sync_init(); - vg_loader_start( _vg_load_full, NULL ); - _vg_gameloop(); - _vg_terminate(); -} - -static void vg_fatal_error( const char *fmt, ... ) -{ - va_list args; - va_start( args, fmt ); - _vg_logx_va( stderr, NULL, "fatal", KRED, fmt, args ); - va_end( args ); - - vg_print_backtrace(); - - SDL_AtomicLock( &vg.sl_status ); - vg.engine_status = k_engine_status_crashed; - SDL_AtomicUnlock( &vg.sl_status ); - - if( vg_thread_purpose() == k_thread_purpose_loader ){ - longjmp( vg.env_loader_exit, 1 ); - } - else{ - vg_error( "There is no jump to the error runner thing yet! bai bai\n" ); - _vg_terminate(); - } -} - -#else /* VG_GAME */ - -#include "vg_log.h" -static void vg_fatal_error( const char *fmt, ... ) -{ - va_list args; - va_start( args, fmt ); - _vg_logx_va( stderr, NULL, "fatal", KRED, fmt, args ); - va_end( args ); - exit(1); -} - -#endif /* VG_GAME */ - -/* - * Graphic cards will check these to force it to use the GPU - */ -u32 NvOptimusEnablement = 0x00000001; -int AmdPowerXpressRequestHighPerformance = 1; - -#include "vg_log.c" - -#endif /* VG_HEADER_H */ +hc vg_build.h +hc vg_build_utils_shader.h +hc vg_m.h + +u32arr vg_pxfont.h +u32arr vg_pxfont_thin.h + +vg_platform.h ?? + +# vg_vorbis.h +# vg_image.h +# vg_depencies.c + +# vg_async.h +# vg_async.c +# vg_audio_dsp.h +# vg_audio_dsp.c +# vg_audio.h +# vg_audio.c +# vg_audio_synth_bird.h +# vg_audio_synth_bird.c +# vg_binstr.h +# vg_binstr.c +# vg_bvh.h +# vg_bvh.c +# vg_camera.h +# vg_camera.c +# vg_engine.c +# vg_engine.h +# vg_log.h +# vg_log.c +# vg_tex.c +# vg_tex.h +# vg_console.h +# vg_console.c +# vg_loader.h +# vg_loader.c +# vg_imgui.h +# vg_imgui.c +# vg_input.h +# vg_input.c +# vg_io.h +# vg_io.c + +# vg_lines.h +# vg_lines.c + +vg_mem.h +vg_mem.c +vg_mem_pool.h +vg_mem_pool.c +vg_mem_queue.h +vg_mem_queue.c +vg_msg.h +vg_msg.c +vg_opt.h +vg_opt.c +vg_perlin.h +vg_perlin.c +vg_string.h +vg_string.c +vg_profiler.h +vg_profiler.c +vg_rigidbody_collision.h +vg_rigidbody_collision.c +vg_rigidbody_constraints.h +vg_rigidbody_constraints.c +vg_rigidbody.h +vg_rigidbody.c +vg_rigidbody_view.h +vg_rigidbody_view.c +vg_shader.h +vg_shader.c + +vg_steam.h +vg_steam_auth.h +vg_steam_friends.h +vg_steam_http.h +vg_steam_networking.h +vg_steam_remote_storage.h +vg_steam_ugc.h +vg_steam_user_stats.h +vg_steam_utils.h + +vg_tool.c +vg_tool.h diff --git a/vg_async.c b/vg_async.c new file mode 100644 index 0000000..0d7809d --- /dev/null +++ b/vg_async.c @@ -0,0 +1,150 @@ +#include "vg_async.h" + +struct vg_async vg_async; + +enum vg_thread_purpose vg_thread_purpose(void); +enum engine_status _vg_engine_status(void); + +/* + * Allocate an asynchronous call with a bit of memory + */ +vg_async_item *vg_async_alloc( u32 size ) +{ + /* ditch out here if engine crashed. this serves as the 'quit checking' */ + if( _vg_engine_status() == k_engine_status_crashed ){ + longjmp( vg.env_loader_exit, 1 ); + } + + SDL_AtomicLock( &vg_async.sl_index ); + + u32 total_allocation = vg_align8(size) + vg_align8(sizeof(vg_async_item)), + remaining = vg_linear_remaining( vg_async.buffer ), + capacity = vg_linear_get_capacity( vg_async.buffer ); + + if( total_allocation > capacity ){ + SDL_AtomicUnlock( &vg_async.sl_index ); + vg_error( "Requested: %umb. Buffer size: %umb\n", + (total_allocation/1024)/1024, + (capacity/1024)/1024 ); + + vg_fatal_error( "async alloc invalid size\n" ); + } + + if( total_allocation > remaining ){ + SDL_AtomicUnlock( &vg_async.sl_index ); + SDL_SemWait( vg_async.sem_wait_for_flush ); + SDL_AtomicLock( &vg_async.sl_index ); + + remaining = vg_linear_remaining( vg_async.buffer ); + capacity = vg_linear_get_capacity( vg_async.buffer ); + } + + void *block = vg_linear_alloc( vg_async.buffer, total_allocation ); + + vg_async_item *entry = block; + entry->next = NULL; + + if( size ) entry->payload = ((u8*)block) + vg_align8(sizeof(vg_async_item)); + else entry->payload = NULL; + + entry->size = size; + entry->fn_runner = NULL; + + if( vg_async.end ){ + vg_async.end->next = entry; + vg_async.end = entry; + }else{ + vg_async.start = entry; + vg_async.end = entry; + } + + SDL_AtomicUnlock( &vg_async.sl_index ); + + return entry; +} + +/* + * Wait until the current stack of async calls is completely flushed out + */ +void vg_async_stall(void) +{ + vg_assert_thread(k_thread_purpose_loader); +#if 0 + vg_info( "async_stall: %d\n", SDL_SemValue( vg_async.sem_wait_for_flush ) ); +#endif + SDL_SemWait( vg_async.sem_wait_for_flush ); +} + +/* + * Mark the call as being filled and ready to go + */ +void vg_async_dispatch( vg_async_item *item, + void (*runner)( void *payload, u32 size ) ) +{ + vg_assert_thread(k_thread_purpose_loader); + if( SDL_SemValue(vg_async.sem_wait_for_flush) ) + SDL_SemWait(vg_async.sem_wait_for_flush); + + SDL_AtomicLock( &vg_async.sl_index ); + item->fn_runner = runner; + SDL_AtomicUnlock( &vg_async.sl_index ); +} + +/* + * Make a simple async call without allocating extra. + */ +void vg_async_call( void (*runner)( void *payload, u32 size ), + void *payload, u32 size ) +{ + vg_assert_thread(k_thread_purpose_loader); + vg_async_item *call = vg_async_alloc(0); + call->payload = payload; + call->size = size; + vg_async_dispatch( call, runner ); +} + +/* + * Run as much of the async buffer as possible + */ +void vg_run_async_checked(void) +{ + SDL_AtomicLock( &vg_async.sl_index ); + + while( vg_async.start ){ + vg_async_item *entry = vg_async.start; + + if( entry->fn_runner ){ + entry->fn_runner( entry->payload, entry->size ); + vg_async.start = entry->next; + + if( vg_async.start == NULL ){ + vg_async.end = NULL; + + vg_linear_clear( vg_async.buffer ); + + if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){ + SDL_SemPost( vg_async.sem_wait_for_flush ); + } + } + } + else{ + SDL_AtomicUnlock( &vg_async.sl_index ); + return; + } + + /* TODO: if exceed max frametime.... */ + } + + if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){ + SDL_SemPost( vg_async.sem_wait_for_flush ); + } + + SDL_AtomicUnlock( &vg_async.sl_index ); +} + +void vg_async_init(void) +{ + vg_async.sem_wait_for_flush = SDL_CreateSemaphore(0); + vg_async.buffer = vg_create_linear_allocator( NULL, 50*1024*1024, + VG_MEMORY_SYSTEM ); +} diff --git a/vg_async.h b/vg_async.h index b3c8a0a..e0a2528 100644 --- a/vg_async.h +++ b/vg_async.h @@ -1,18 +1,15 @@ -/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved * * primateves that you use when you need to run something from another thread * back in the main loop of vg, at the start of each frame */ -#ifndef VG_ASYNC_H -#define VG_ASYNC_H +#pragma once +#include "vg_engine.h" -#define VG_GAME -#include "vg/vg.h" static void vg_assert_thread( enum vg_thread_purpose required ); typedef struct vg_async_item vg_async_item; - struct vg_async_item{ vg_async_item *next; @@ -22,7 +19,8 @@ struct vg_async_item{ void (*fn_runner)( void *payload, u32 size ); }; -struct vg_async{ +struct vg_async +{ void *buffer; vg_async_item *start, *end; @@ -30,157 +28,23 @@ struct vg_async{ SDL_sem *sem_wait_for_flush; SDL_SpinLock sl_index; } -static vg_async; - -static enum vg_thread_purpose vg_thread_purpose(void); -static enum engine_status _vg_engine_status(void); - -/* - * Allocate an asynchronous call with a bit of memory - */ -static vg_async_item *vg_async_alloc( u32 size ) -{ - /* ditch out here if engine crashed. this serves as the 'quit checking' */ - if( _vg_engine_status() == k_engine_status_crashed ){ - assert( vg_thread_purpose() == k_thread_purpose_loader ); - longjmp( vg.env_loader_exit, 1 ); - } - - SDL_AtomicLock( &vg_async.sl_index ); - - u32 total_allocation = vg_align8(size) + vg_align8(sizeof(vg_async_item)), - remaining = vg_linear_remaining( vg_async.buffer ), - capacity = vg_linear_get_capacity( vg_async.buffer ); - - if( total_allocation > capacity ){ - SDL_AtomicUnlock( &vg_async.sl_index ); - vg_error( "Requested: %umb. Buffer size: %umb\n", - (total_allocation/1024)/1024, - (capacity/1024)/1024 ); - - vg_fatal_error( "async alloc invalid size\n" ); - } - - if( total_allocation > remaining ){ - SDL_AtomicUnlock( &vg_async.sl_index ); - SDL_SemWait( vg_async.sem_wait_for_flush ); - SDL_AtomicLock( &vg_async.sl_index ); +extern vg_async; - remaining = vg_linear_remaining( vg_async.buffer ); - capacity = vg_linear_get_capacity( vg_async.buffer ); +/* TODO: Docu */ - assert( remaining == capacity ); - assert( vg_async.start == NULL ); - assert( vg_async.end == NULL ); - } - - void *block = vg_linear_alloc( vg_async.buffer, total_allocation ); - - vg_async_item *entry = block; - entry->next = NULL; - - if( size ) entry->payload = ((u8*)block) + vg_align8(sizeof(vg_async_item)); - else entry->payload = NULL; - - entry->size = size; - entry->fn_runner = NULL; - - if( vg_async.end ){ - vg_async.end->next = entry; - vg_async.end = entry; - }else{ - vg_async.start = entry; - vg_async.end = entry; - } - - SDL_AtomicUnlock( &vg_async.sl_index ); - - return entry; -} - -/* - * Wait until the current stack of async calls is completely flushed out - */ -static void vg_async_stall(void){ - vg_assert_thread(k_thread_purpose_loader); -#if 0 - vg_info( "async_stall: %d\n", SDL_SemValue( vg_async.sem_wait_for_flush ) ); -#endif - SDL_SemWait( vg_async.sem_wait_for_flush ); -} +void vg_async_call( void (*runner)( void *payload, u32 size ), + void *payload, u32 size ); +void vg_run_async_checked(void); +void vg_async_init(void); +vg_async_item *vg_async_alloc( u32 size ); /* * Mark the call as being filled and ready to go */ -static void vg_async_dispatch( vg_async_item *item, - void (*runner)( void *payload, u32 size ) ) -{ - vg_assert_thread(k_thread_purpose_loader); - if( SDL_SemValue(vg_async.sem_wait_for_flush) ) - SDL_SemWait(vg_async.sem_wait_for_flush); - - SDL_AtomicLock( &vg_async.sl_index ); - item->fn_runner = runner; - SDL_AtomicUnlock( &vg_async.sl_index ); -} - -/* - * Make a simple async call without allocating extra. - */ -static void vg_async_call( void (*runner)( void *payload, u32 size ), - void *payload, u32 size ) -{ - vg_assert_thread(k_thread_purpose_loader); - vg_async_item *call = vg_async_alloc(0); - call->payload = payload; - call->size = size; - vg_async_dispatch( call, runner ); -} +void vg_async_dispatch( vg_async_item *item, + void (*runner)( void *payload, u32 size ) ); /* - * Run as much of the async buffer as possible + * Wait until the current stack of async calls is completely flushed out */ -static void vg_run_async_checked(void) -{ - SDL_AtomicLock( &vg_async.sl_index ); - - while( vg_async.start ){ - vg_async_item *entry = vg_async.start; - - if( entry->fn_runner ){ - entry->fn_runner( entry->payload, entry->size ); - vg_async.start = entry->next; - - if( vg_async.start == NULL ){ - vg_async.end = NULL; - - vg_linear_clear( vg_async.buffer ); - - if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){ - SDL_SemPost( vg_async.sem_wait_for_flush ); - } - } - } - else{ - SDL_AtomicUnlock( &vg_async.sl_index ); - return; - } - - /* TODO: if exceed max frametime.... */ - } - - if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){ - SDL_SemPost( vg_async.sem_wait_for_flush ); - } - - SDL_AtomicUnlock( &vg_async.sl_index ); -} - -static void vg_async_init(void) -{ - vg_async.sem_wait_for_flush = SDL_CreateSemaphore(0); - vg_async.buffer = vg_create_linear_allocator( NULL, 50*1024*1024, - VG_MEMORY_SYSTEM ); -} - -#endif /* VG_ASYNC_H */ +void vg_async_stall(void); diff --git a/vg_audio.c b/vg_audio.c new file mode 100644 index 0000000..1611a62 --- /dev/null +++ b/vg_audio.c @@ -0,0 +1,1212 @@ +#include "vg_audio.h" +#include "vg_audio_dsp.h" +#include "vg_platform.h" +#include "vg_io.h" +#include "vg_m.h" +#include "vg_console.h" +#include "vg_profiler.h" +#include "vg_audio_synth_bird.h" +#include "vg_vorbis.h" +#include + +struct vg_audio_system vg_audio = +{ + .external_global_volume = 1.0f, + .dsp_enabled = 1 +}; + +static struct vg_profile + _vg_prof_audio_decode = {.mode = k_profile_mode_accum, + .name = "[T2] audio_decode()"}, + _vg_prof_audio_mix = {.mode = k_profile_mode_accum, + .name = "[T2] audio_mix()"}, + _vg_prof_dsp = {.mode = k_profile_mode_accum, + .name = "[T2] dsp_process()"}, + vg_prof_audio_decode, + vg_prof_audio_mix, + vg_prof_audio_dsp; + +/* + * These functions are called from the main thread and used to prevent bad + * access. TODO: They should be no-ops in release builds. + */ +static int audio_lock_checker_load(void) +{ + int value; + SDL_AtomicLock( &vg_audio.sl_checker ); + value = vg_audio.sync_locked; + SDL_AtomicUnlock( &vg_audio.sl_checker ); + return value; +} + +static void audio_lock_checker_store( int value ) +{ + SDL_AtomicLock( &vg_audio.sl_checker ); + vg_audio.sync_locked = value; + SDL_AtomicUnlock( &vg_audio.sl_checker ); +} + +static void audio_require_lock(void) +{ + if( audio_lock_checker_load() ) + return; + + vg_error( "Modifying sound effects systems requires locking\n" ); + abort(); +} + +void audio_lock(void) +{ + SDL_AtomicLock( &vg_audio.sl_sync ); + audio_lock_checker_store(1); +} + +void audio_unlock(void) +{ + audio_lock_checker_store(0); + SDL_AtomicUnlock( &vg_audio.sl_sync ); +} + +static void audio_mixer_callback( void *user, u8 *stream, int frame_count ); +void vg_audio_device_init(void) +{ + SDL_AudioSpec spec_desired, spec_got; + spec_desired.callback = audio_mixer_callback; + spec_desired.channels = 2; + spec_desired.format = AUDIO_F32; + spec_desired.freq = 44100; + spec_desired.padding = 0; + spec_desired.samples = AUDIO_FRAME_SIZE; + spec_desired.silence = 0; + spec_desired.size = 0; + spec_desired.userdata = NULL; + + vg_audio.sdl_output_device = + SDL_OpenAudioDevice( vg_audio.device_choice.buffer, 0, + &spec_desired, &spec_got,0 ); + + vg_info( "Start audio device (%u, F32, %u) @%s\n", + spec_desired.freq, + AUDIO_FRAME_SIZE, + vg_audio.device_choice.buffer ); + + if( vg_audio.sdl_output_device ){ + SDL_PauseAudioDevice( vg_audio.sdl_output_device, 0 ); + vg_success( "Unpaused device %d.\n", vg_audio.sdl_output_device ); + } + else{ + vg_error( + "SDL_OpenAudioDevice failed. Your default audio device must support:\n" + " Frequency: 44100 hz\n" + " Buffer size: 512\n" + " Channels: 2\n" + " Format: s16 or f32\n" ); + } +} + +void vg_audio_register(void) +{ + vg_console_reg_var( "debug_audio", &vg_audio.debug_ui, + k_var_dtype_i32, VG_VAR_CHEAT ); + vg_console_reg_var( "debug_dsp", &vg_audio.debug_dsp, + k_var_dtype_i32, VG_VAR_CHEAT ); + vg_console_reg_var( "volume", &vg_audio.external_global_volume, + k_var_dtype_f32, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_audio_device", &vg_audio.device_choice, + k_var_dtype_str, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_dsp", &vg_audio.dsp_enabled, + k_var_dtype_i32, VG_VAR_PERSISTENT ); +} + +void vg_audio_init(void) +{ + /* allocate memory */ + /* 32mb fixed */ + vg_audio.audio_pool = + vg_create_linear_allocator( vg_mem.rtmemory, 1024*1024*32, + VG_MEMORY_SYSTEM ); + + /* fixed */ + u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; + vg_audio.decode_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); + + vg_dsp_init(); + vg_audio_device_init(); +} + +void vg_audio_free(void) +{ + vg_dsp_free(); + SDL_CloseAudioDevice( vg_audio.sdl_output_device ); +} + +/* + * thread 1 + */ + +#define AUDIO_EDIT_VOLUME_SLOPE 0x1 +#define AUDIO_EDIT_VOLUME 0x2 +#define AUDIO_EDIT_LFO_PERIOD 0x4 +#define AUDIO_EDIT_LFO_WAVE 0x8 +#define AUDIO_EDIT_LFO_ATTACHMENT 0x10 +#define AUDIO_EDIT_SPACIAL 0x20 +#define AUDIO_EDIT_OWNERSHIP 0x40 +#define AUDIO_EDIT_SAMPLING_RATE 0x80 + +void audio_channel_init( audio_channel *ch, audio_clip *clip, u32 flags ) +{ + audio_require_lock(); + ch->group = 0; + ch->world_id = 0; + ch->source = clip; + ch->flags = flags; + ch->colour = 0x00333333; + + if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_bird ) + strcpy( ch->name, "[array]" ); + else if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_gen ) + strcpy( ch->name, "[program]" ); + else + vg_strncpy( clip->path, ch->name, 32, k_strncpy_always_add_null ); + + ch->allocated = 1; + + ch->editable_state.relinquished = 0; + ch->editable_state.volume = 1.0f; + ch->editable_state.volume_target = 1.0f; + ch->editable_state.pan = 0.0f; + ch->editable_state.pan_target = 0.0f; + ch->editable_state.volume_rate = 0; + ch->editable_state.pan_rate = 0; + v4_copy((v4f){0.0f,0.0f,0.0f,1.0f},ch->editable_state.spacial_falloff); + ch->editable_state.lfo = NULL; + ch->editable_state.lfo_amount = 0.0f; + ch->editable_state.sampling_rate = 1.0f; + ch->editble_state_write_mask = 0x00; +} + +void audio_channel_group( audio_channel *ch, u16 group ) +{ + audio_require_lock(); + ch->group = group; + ch->colour = (((u32)group * 29986577) & 0x00ffffff) | 0xff000000; +} + +void audio_channel_world( audio_channel *ch, u8 world_id ) +{ + audio_require_lock(); + ch->world_id = world_id; +} + +audio_channel *audio_get_first_idle_channel(void) +{ + audio_require_lock(); + for( int i=0; iallocated ){ + return ch; + } + } + + return NULL; +} + +audio_channel *audio_get_group_idle_channel( u16 group, u32 max_count ) +{ + audio_require_lock(); + u32 count = 0; + audio_channel *dest = NULL; + + for( int i=0; iallocated ){ + if( ch->group == group ){ + count ++; + } + } + else{ + if( !dest ) + dest = ch; + } + } + + if( dest && (count < max_count) ){ + return dest; + } + + return NULL; +} + +audio_channel *audio_get_group_first_active_channel( u16 group ) +{ + audio_require_lock(); + for( int i=0; iallocated && (ch->group == group) ) + return ch; + } + return NULL; +} + +int audio_channel_finished( audio_channel *ch ) +{ + audio_require_lock(); + if( ch->readable_activity == k_channel_activity_end ) + return 1; + else + return 0; +} + +audio_channel *audio_relinquish_channel( audio_channel *ch ) +{ + audio_require_lock(); + ch->editable_state.relinquished = 1; + ch->editble_state_write_mask |= AUDIO_EDIT_OWNERSHIP; + return NULL; +} + +void audio_channel_slope_volume( audio_channel *ch, f32 length, f32 new_vol ) +{ + audio_require_lock(); + ch->editable_state.volume_target = new_vol; + ch->editable_state.volume_rate = length * 44100.0f; + ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME_SLOPE; +} + +void audio_channel_set_sampling_rate( audio_channel *ch, float rate ) +{ + audio_require_lock(); + ch->editable_state.sampling_rate = rate; + ch->editble_state_write_mask |= AUDIO_EDIT_SAMPLING_RATE; +} + +void audio_channel_edit_volume( audio_channel *ch, f32 new_vol, int instant ) +{ + audio_require_lock(); + if( instant ){ + ch->editable_state.volume = new_vol; + ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME; + } + else{ + audio_channel_slope_volume( ch, 0.05f, new_vol ); + } +} + +audio_channel *audio_channel_fadeout( audio_channel *ch, float length ) +{ + audio_require_lock(); + audio_channel_slope_volume( ch, length, 0.0f ); + return audio_relinquish_channel( ch ); +} + +void audio_channel_fadein( audio_channel *ch, float length ) +{ + audio_require_lock(); + audio_channel_edit_volume( ch, 0.0f, 1 ); + audio_channel_slope_volume( ch, length, 1.0f ); +} + +audio_channel *audio_channel_crossfade( audio_channel *ch, + audio_clip *new_clip, + float length, u32 flags ) +{ + audio_require_lock(); + u32 cursor = 0; + + if( ch ) + ch = audio_channel_fadeout( ch, length ); + + audio_channel *replacement = audio_get_first_idle_channel(); + + if( replacement ){ + audio_channel_init( replacement, new_clip, flags ); + audio_channel_fadein( replacement, length ); + } + + return replacement; +} + +void audio_channel_sidechain_lfo( audio_channel *ch, int lfo_id, f32 amount ) +{ + audio_require_lock(); + ch->editable_state.lfo = &vg_audio.oscillators[ lfo_id ]; + ch->editable_state.lfo_amount = amount; + ch->editble_state_write_mask |= AUDIO_EDIT_LFO_ATTACHMENT; +} + +void audio_channel_set_spacial( audio_channel *ch, v3f co, float range ) +{ + audio_require_lock(); + if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ + v3_copy( co, ch->editable_state.spacial_falloff ); + + if( range == 0.0f ) + ch->editable_state.spacial_falloff[3] = 1.0f; + else + ch->editable_state.spacial_falloff[3] = 1.0f/range; + + ch->editble_state_write_mask |= AUDIO_EDIT_SPACIAL; + } + else{ + vg_warn( "Tried to set spacialization paramaters for 2D channel (%s)\n", + ch->name ); + } +} + +int audio_oneshot_3d( audio_clip *clip, v3f position, f32 range, f32 volume ) +{ + audio_require_lock(); + audio_channel *ch = audio_get_first_idle_channel(); + + if( ch ){ + audio_channel_init( ch, clip, AUDIO_FLAG_SPACIAL_3D ); + audio_channel_set_spacial( ch, position, range ); + audio_channel_edit_volume( ch, volume, 1 ); + ch = audio_relinquish_channel( ch ); + + return 1; + } + else + return 0; +} + +int audio_oneshot( audio_clip *clip, f32 volume, f32 pan ) +{ + audio_require_lock(); + audio_channel *ch = audio_get_first_idle_channel(); + + if( ch ){ + audio_channel_init( ch, clip, 0x00 ); + audio_channel_edit_volume( ch, volume, 1 ); + ch = audio_relinquish_channel( ch ); + + return 1; + } + else + return 0; +} + +void audio_set_lfo_wave( int id, enum lfo_wave_type type, f32 coefficient ) +{ + audio_require_lock(); + audio_lfo *lfo = &vg_audio.oscillators[ id ]; + lfo->editable_state.polynomial_coefficient = coefficient; + lfo->editable_state.wave_type = type; + + lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_WAVE; +} + +void audio_set_lfo_frequency( int id, float freq ) +{ + audio_require_lock(); + audio_lfo *lfo = &vg_audio.oscillators[ id ]; + lfo->editable_state.period = 44100.0f / freq; + lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_PERIOD; +} + + +/* + * Committers + * ----------------------------------------------------------------------------- + */ +int audio_channel_load_source( audio_channel *ch ) +{ + u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; + + if( format == k_audio_format_vorbis ){ + /* Setup vorbis decoder */ + u32 index = ch - vg_audio.channels; + + u8 *buf = (u8*)vg_audio.decode_buffer, + *loc = &buf[AUDIO_DECODE_SIZE*index]; + + stb_vorbis_alloc alloc = { + .alloc_buffer = (char *)loc, + .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE + }; + + int err; + stb_vorbis *decoder = stb_vorbis_open_memory( + ch->source->data, + ch->source->size, &err, &alloc ); + + if( !decoder ){ + vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", + ch->source->path, err ); + return 0; + } + else{ + ch->source_length = stb_vorbis_stream_length_in_samples( decoder ); + ch->handle.vorbis = decoder; + } + } + else if( format == k_audio_format_bird ){ + u32 index = ch - vg_audio.channels; + + u8 *buf = (u8*)vg_audio.decode_buffer; + struct synth_bird *loc = (void *)&buf[AUDIO_DECODE_SIZE*index]; + + memcpy( loc, ch->source->data, ch->source->size ); + synth_bird_reset( loc ); + + ch->handle.bird = loc; + ch->source_length = synth_bird_get_length_in_samples( loc ); + } + else if( format == k_audio_format_stereo ){ + ch->source_length = ch->source->size / 2; + } + else if( format == k_audio_format_gen ){ + ch->source_length = 0xffffffff; + } + else{ + ch->source_length = ch->source->size; + } + + return 1; +} + +static void audio_decode_uncompressed_mono( i16 *src, u32 count, float *dst ) +{ + for( u32 i=0; itime ++; + + if( lfo->time >= lfo->_.period ) + lfo->time = 0; + + float t = lfo->time; + t /= (float)lfo->_.period; + + if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ + /* + * # + * # # + * # # + * # # + * ### # ### + * ## # + * # # + * # # + * ## + */ + + t *= 2.0f; + t -= 1.0f; + + return (( 2.0f * lfo->sqrt_polynomial_coefficient * t ) / + /* --------------------------------------- */ + ( 1.0f + lfo->_.polynomial_coefficient * t*t ) + + ) * (1.0f-fabsf(t)); + } + else{ + return 0.0f; + } +} + +static void audio_channel_get_samples( audio_channel *ch, + u32 count, float *buf ) +{ + vg_profile_begin( &_vg_prof_audio_decode ); + + u32 remaining = count; + u32 buffer_pos = 0; + + u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; + + while( remaining ){ + u32 samples_this_run = VG_MIN(remaining, ch->source_length - ch->cursor); + remaining -= samples_this_run; + + float *dst = &buf[ buffer_pos * 2 ]; + + if( format == k_audio_format_stereo ){ + for( int i=0;ihandle.vorbis, + dst, + samples_this_run ); + + if( read_samples != samples_this_run ){ + vg_warn( "Invalid samples read (%s)\n", ch->source->path ); + + for( int i=0; ihandle.bird, dst, samples_this_run ); + } + else if( format == k_audio_format_gen ){ + void (*fn)( void *data, f32 *buf, u32 count ) = ch->source->func; + fn( ch->source->data, dst, samples_this_run ); + } + else{ + i16 *src_buffer = ch->source->data, + *src = &src_buffer[ch->cursor]; + + audio_decode_uncompressed_mono( src, samples_this_run, dst ); + } + + ch->cursor += samples_this_run; + buffer_pos += samples_this_run; + + if( (ch->flags & AUDIO_FLAG_LOOP) && remaining ){ + if( format == k_audio_format_vorbis ) + stb_vorbis_seek_start( ch->handle.vorbis ); + else if( format == k_audio_format_bird ) + synth_bird_reset( ch->handle.bird ); + + ch->cursor = 0; + continue; + } + else + break; + } + + while( remaining ){ + buf[ buffer_pos*2 + 0 ] = 0.0f; + buf[ buffer_pos*2 + 1 ] = 0.0f; + buffer_pos ++; + + remaining --; + } + + vg_profile_end( &_vg_prof_audio_decode ); +} + +static void audio_channel_mix( audio_channel *ch, float *buffer ) +{ + float framevol_l = vg_audio.internal_global_volume, + framevol_r = vg_audio.internal_global_volume; + + float frame_samplerate = ch->_.sampling_rate; + + if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ + v3f delta; + v3_sub( ch->_.spacial_falloff, vg_audio.internal_listener_pos, delta ); + + float dist = v3_length( delta ), + vol = vg_maxf( 0.0f, 1.0f - ch->_.spacial_falloff[3]*dist ); + + if( dist <= 0.01f ){ + + } + else{ + v3_muls( delta, 1.0f/dist, delta ); + float pan = v3_dot( vg_audio.internal_listener_ears, delta ); + vol = powf( vol, 5.0f ); + + framevol_l *= (vol * 0.5f) * (1.0f - pan); + framevol_r *= (vol * 0.5f) * (1.0f + pan); + + if( !(ch->source->flags & AUDIO_FLAG_NO_DOPPLER) ){ + const float vs = 323.0f; + + float dv = v3_dot(delta,vg_audio.internal_listener_velocity); + float doppler = (vs+dv)/vs; + doppler = vg_clampf( doppler, 0.6f, 1.4f ); + + if( fabsf(doppler-1.0f) > 0.01f ) + frame_samplerate *= doppler; + } + } + + if( !vg_validf( framevol_l ) || + !vg_validf( framevol_r ) || + !vg_validf( frame_samplerate ) ){ + vg_fatal_error( "Invalid sampling conditions.\n" + "This crash is to protect your ears.\n" + " channel: %p (%s)\n" + " sample_rate: %f\n" + " volume: L%f R%f\n" + " listener: %.2f %.2f %.2f [%.2f %.2f %.2f]\n", + ch, ch->name, frame_samplerate, + framevol_l, framevol_r, + vg_audio.internal_listener_pos[0], + vg_audio.internal_listener_pos[1], + vg_audio.internal_listener_pos[2], + vg_audio.internal_listener_ears[0], + vg_audio.internal_listener_ears[1], + vg_audio.internal_listener_ears[2] + ); + } + } + + u32 buffer_length = AUDIO_MIX_FRAME_SIZE; + if( frame_samplerate != 1.0f ){ + float l = ceilf( (float)(AUDIO_MIX_FRAME_SIZE) * frame_samplerate ); + buffer_length = l+1; + } + + float pcf[ AUDIO_MIX_FRAME_SIZE * 2 * 2 ]; + + audio_channel_get_samples( ch, buffer_length, pcf ); + + vg_profile_begin( &_vg_prof_audio_mix ); + + float volume_movement = ch->volume_movement; + float const fvolume_rate = vg_maxf( 1.0f, ch->_.volume_rate ); + const float inv_volume_rate = 1.0f/fvolume_rate; + + float volume = ch->_.volume; + const float volume_start = ch->volume_movement_start; + const float volume_target = ch->_.volume_target; + + for( u32 j=0; j_.lfo ) + vol_norm *= 1.0f + audio_lfo_pull_sample(ch->_.lfo) * ch->_.lfo_amount; + + float vol_l = vol_norm * framevol_l, + vol_r = vol_norm * framevol_r, + sample_l, + sample_r; + + if( frame_samplerate != 1.0f ){ + /* absolutely garbage resampling, but it will do + */ + + float sample_index = frame_samplerate * (float)j; + float t = vg_fractf( sample_index ); + + u32 i0 = floorf( sample_index ), + i1 = i0+1; + + sample_l = pcf[ i0*2+0 ]*(1.0f-t) + pcf[ i1*2+0 ]*t; + sample_r = pcf[ i0*2+1 ]*(1.0f-t) + pcf[ i1*2+1 ]*t; + } + else{ + sample_l = pcf[ j*2+0 ]; + sample_r = pcf[ j*2+1 ]; + } + + buffer[ j*2+0 ] += sample_l * vol_l; + buffer[ j*2+1 ] += sample_r * vol_r; + } + + ch->volume_movement += AUDIO_MIX_FRAME_SIZE; + ch->volume_movement = VG_MIN( ch->volume_movement, ch->_.volume_rate ); + ch->_.volume = volume; + + vg_profile_end( &_vg_prof_audio_mix ); +} + +static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ + /* + * Copy data and move edit flags to commit flags + * ------------------------------------------------------------- */ + audio_lock(); + int use_dsp = vg_audio.dsp_enabled; + + v3_copy( vg_audio.external_listener_pos, vg_audio.internal_listener_pos ); + v3_copy( vg_audio.external_listener_ears, vg_audio.internal_listener_ears ); + v3_copy( vg_audio.external_lister_velocity, + vg_audio.internal_listener_velocity ); + vg_audio.internal_global_volume = vg_audio.external_global_volume; + + for( int i=0; iallocated ) + continue; + + if( ch->activity == k_channel_activity_alive ){ + if( (ch->cursor >= ch->source_length) && + !(ch->flags & AUDIO_FLAG_LOOP) ) + { + ch->activity = k_channel_activity_end; + } + } + + /* process relinquishments */ + if( (ch->activity != k_channel_activity_reset) && ch->_.relinquished ){ + if( (ch->activity == k_channel_activity_end) + || (ch->_.volume == 0.0f) + || (ch->activity == k_channel_activity_error) ) + { + ch->_.relinquished = 0; + ch->allocated = 0; + ch->activity = k_channel_activity_reset; + continue; + } + } + + /* process new channels */ + if( ch->activity == k_channel_activity_reset ){ + ch->_ = ch->editable_state; + ch->cursor = 0; + ch->source_length = 0; + ch->activity = k_channel_activity_wake; + } + + if( ch->editble_state_write_mask & AUDIO_EDIT_OWNERSHIP ) + ch->_.relinquished = ch->editable_state.relinquished; + else + ch->editable_state.relinquished = ch->_.relinquished; + + + if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME ){ + ch->_.volume = ch->editable_state.volume; + ch->_.volume_target = ch->editable_state.volume; + } + else{ + ch->editable_state.volume = ch->_.volume; + } + + + if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME_SLOPE ){ + ch->volume_movement_start = ch->_.volume; + ch->volume_movement = 0; + + ch->_.volume_target = ch->editable_state.volume_target; + ch->_.volume_rate = ch->editable_state.volume_rate; + } + else{ + ch->editable_state.volume_target = ch->_.volume_target; + ch->editable_state.volume_rate = ch->_.volume_rate; + } + + + if( ch->editble_state_write_mask & AUDIO_EDIT_SAMPLING_RATE ) + ch->_.sampling_rate = ch->editable_state.sampling_rate; + else + ch->editable_state.sampling_rate = ch->_.sampling_rate; + + + if( ch->editble_state_write_mask & AUDIO_EDIT_LFO_ATTACHMENT ){ + ch->_.lfo = ch->editable_state.lfo; + ch->_.lfo_amount = ch->editable_state.lfo_amount; + } + else{ + ch->editable_state.lfo = ch->_.lfo; + ch->editable_state.lfo_amount = ch->_.lfo_amount; + } + + + if( ch->editble_state_write_mask & AUDIO_EDIT_SPACIAL ) + v4_copy( ch->editable_state.spacial_falloff,ch->_.spacial_falloff ); + else + v4_copy( ch->_.spacial_falloff,ch->editable_state.spacial_falloff ); + + + /* currently readonly, i guess */ + ch->editable_state.pan_target = ch->_.pan_target; + ch->editable_state.pan = ch->_.pan; + ch->editble_state_write_mask = 0x00; + } + + for( int i=0; ieditble_state_write_mask & AUDIO_EDIT_LFO_WAVE ){ + lfo->_.wave_type = lfo->editable_state.wave_type; + + if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ + lfo->_.polynomial_coefficient = + lfo->editable_state.polynomial_coefficient; + lfo->sqrt_polynomial_coefficient = + sqrtf(lfo->_.polynomial_coefficient); + } + } + + if( lfo->editble_state_write_mask & AUDIO_EDIT_LFO_PERIOD ){ + if( lfo->_.period ){ + float t = lfo->time; + t/= (float)lfo->_.period; + + lfo->_.period = lfo->editable_state.period; + lfo->time = lfo->_.period * t; + } + else{ + lfo->time = 0; + lfo->_.period = lfo->editable_state.period; + } + } + + lfo->editble_state_write_mask = 0x00; + } + + dsp_update_tunings(); + audio_unlock(); + + /* + * Process spawns + * ------------------------------------------------------------- */ + for( int i=0; iactivity == k_channel_activity_wake ){ + if( audio_channel_load_source( ch ) ) + ch->activity = k_channel_activity_alive; + else + ch->activity = k_channel_activity_error; + } + } + + /* + * Mix everything + * -------------------------------------------------------- */ + int frame_count = byte_count/(2*sizeof(float)); + + /* Clear buffer */ + float *pOut32F = (float *)stream; + for( int i=0; itime_startframe = lfo->time; + } + + for( int i=0; iactivity == k_channel_activity_alive ){ + if( ch->_.lfo ) + ch->_.lfo->time = ch->_.lfo->time_startframe; + + u32 remaining = frame_count, + subpos = 0; + + while( remaining ){ + audio_channel_mix( ch, pOut32F+subpos ); + remaining -= AUDIO_MIX_FRAME_SIZE; + subpos += AUDIO_MIX_FRAME_SIZE*2; + } + } + } + + if( use_dsp ){ + vg_profile_begin( &_vg_prof_dsp ); + for( int i=0; ireadable_activity = ch->activity; + } + + /* Profiling information + * ----------------------------------------------- */ + vg_profile_increment( &_vg_prof_audio_decode ); + vg_profile_increment( &_vg_prof_audio_mix ); + vg_profile_increment( &_vg_prof_dsp ); + + vg_prof_audio_mix = _vg_prof_audio_mix; + vg_prof_audio_decode = _vg_prof_audio_decode; + vg_prof_audio_dsp = _vg_prof_dsp; + + vg_audio.samples_last = frame_count; + + if( vg_audio.debug_dsp ) + vg_dsp_update_texture(); + + audio_unlock(); +} + +void audio_clip_load( audio_clip *clip, void *lin_alloc ) +{ + if( lin_alloc == NULL ) + lin_alloc = vg_audio.audio_pool; + + if( vg_audio.always_keep_compressed ) + { + if( (clip->flags & AUDIO_FLAG_FORMAT) != k_audio_format_bird ){ + clip->flags &= ~AUDIO_FLAG_FORMAT; + clip->flags |= k_audio_format_vorbis; + } + } + + /* load in directly */ + u32 format = clip->flags & AUDIO_FLAG_FORMAT; + + /* TODO: This contains audio_lock() and unlock, but i don't know why + * can probably remove them. Low priority to check this */ + + /* TODO: packed files for vorbis etc, should take from data if its not not + * NULL when we get the clip + */ + + if( format == k_audio_format_vorbis ){ + if( !clip->path ){ + vg_fatal_error( "No path specified, embeded vorbis unsupported" ); + } + + audio_lock(); + clip->data = vg_file_read( lin_alloc, clip->path, &clip->size ); + audio_unlock(); + + if( !clip->data ) + vg_fatal_error( "Audio failed to load" ); + + float mb = (float)(clip->size) / (1024.0f*1024.0f); + vg_info( "Loaded audio clip '%s' (%.1fmb)\n", clip->path, mb ); + } + else if( format == k_audio_format_stereo ){ + vg_fatal_error( "Unsupported format (Stereo uncompressed)" ); + } + else if( format == k_audio_format_bird ){ + if( !clip->data ){ + vg_fatal_error( "No data, external birdsynth unsupported" ); + } + + u32 total_size = clip->size + sizeof(struct synth_bird); + total_size -= sizeof(struct synth_bird_settings); + total_size = vg_align8( total_size ); + + if( total_size > AUDIO_DECODE_SIZE ) + vg_fatal_error( "Bird coding too long\n" ); + + struct synth_bird *bird = vg_linear_alloc( lin_alloc, total_size ); + memcpy( &bird->settings, clip->data, clip->size ); + + clip->data = bird; + clip->size = total_size; + + vg_info( "Loaded bird synthesis pattern (%u bytes)\n", total_size ); + } + else{ + if( !clip->path ){ + vg_fatal_error( "No path specified, embeded mono unsupported" ); + } + + vg_linear_clear( vg_mem.scratch ); + u32 fsize; + + stb_vorbis_alloc alloc = { + .alloc_buffer = vg_linear_alloc( vg_mem.scratch, AUDIO_DECODE_SIZE ), + .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE + }; + + void *filedata = vg_file_read( vg_mem.scratch, clip->path, &fsize ); + + int err; + stb_vorbis *decoder = stb_vorbis_open_memory( + filedata, fsize, &err, &alloc ); + + if( !decoder ){ + vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", + clip->path, err ); + vg_fatal_error( "Vorbis decode error" ); + } + + /* only mono is supported in uncompressed */ + u32 length_samples = stb_vorbis_stream_length_in_samples( decoder ), + data_size = length_samples * sizeof(i16); + + audio_lock(); + clip->data = vg_linear_alloc( lin_alloc, vg_align8(data_size) ); + clip->size = length_samples; + audio_unlock(); + + int read_samples = stb_vorbis_get_samples_i16_downmixed( + decoder, clip->data, length_samples ); + + if( read_samples != length_samples ) + vg_fatal_error( "Decode error" ); + +#if 0 + float mb = (float)(data_size) / (1024.0f*1024.0f); + vg_info( "Loaded audio clip '%s' (%.1fmb) %u samples\n", clip->path, mb, + length_samples ); +#endif + } +} + +void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) +{ + for( int i=0; idata && clip->size ) + return; + + audio_unlock(); + vg_fatal_error( "Must load audio clip before playing! \n" ); +} + +/* + * Debugging + */ + +void audio_debug_ui( + +#ifdef VG_3D + m4x4f +#else + m3x3f +#endif + mtx_pv ){ + + if( !vg_audio.debug_ui ) + return; + + audio_lock(); + + glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); + glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 256, 256, + GL_RGBA, GL_UNSIGNED_BYTE, + vg_dsp.view_texture_buffer ); + + /* + * Profiler + * ----------------------------------------------------------------------- + */ + + float budget = ((double)vg_audio.samples_last / 44100.0) * 1000.0; + vg_profile_drawn( (struct vg_profile *[]){ &vg_prof_audio_decode, + &vg_prof_audio_mix, + &vg_prof_audio_dsp}, 3, + budget, (ui_rect){ 4, VG_PROFILE_SAMPLE_COUNT*2 + 8, + 512, 0 }, 0, 0 ); + + + char perf[128]; + + /* Draw UI */ + ui_rect window = { + 0, + 0, + 800, + AUDIO_CHANNELS * 18 + }; + + if( vg_audio.debug_dsp ){ + ui_rect view_thing = { 4, vg.window_y-512-4, 512, 512 }; + ui_image( view_thing, vg_dsp.view_texture ); + } + + ui_rect overlap_buffer[ AUDIO_CHANNELS ]; + u32 overlap_length = 0; + + /* Draw audio stack */ + for( int i=0; iallocated ){ + ui_fill( row, 0x50333333 ); + continue; + } + + const char *formats[] = + { + " mono ", + " stereo ", + " vorbis ", + " none0 ", + " none1 ", + " none2 ", + " none3 ", + " none4 ", + "synth:bird", + " none5 ", + " none6 ", + " none7 ", + " none8 ", + " none9 ", + " none10 ", + " none11 ", + }; + + const char *activties[] = + { + "reset", + "wake ", + "alive", + "end ", + "error" + }; + + u32 format_index = (ch->source->flags & AUDIO_FLAG_FORMAT)>>9; + + snprintf( perf, 127, "%02d[%#04x.%#06x]%c%c%cD %s [%s] %4.2fv'%s'", + i, + ch->world_id, ch->group, + (ch->editable_state.relinquished)? 'r': '_', + 0? 'r': '_', + 0? '3': '2', + formats[format_index], + activties[ch->readable_activity], + ch->editable_state.volume, + ch->name ); + + ui_fill( row, 0xa0000000 | ch->colour ); + ui_text( row, perf, 1, k_ui_align_middle_left, 0 ); + +#ifdef VG_3D + if( AUDIO_FLAG_SPACIAL_3D ){ + v4f wpos; + v3_copy( ch->editable_state.spacial_falloff, wpos ); + + wpos[3] = 1.0f; + m4x4_mulv( mtx_pv, wpos, wpos ); + + if( wpos[3] > 0.0f ){ + v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos ); + v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos ); + + ui_rect wr; + wr[0] = vg_clampf(wpos[0] * vg.window_x, -32000.0f,32000.0f); + wr[1] = vg_clampf((1.0f-wpos[1]) * vg.window_y,-32000.0f,32000.0f); + wr[2] = 1000; + wr[3] = 17; + + for( int j=0; j<12; j++ ){ + int collide = 0; + for( int k=0; k= wk[0])) && + ((wr[1] <= wk[1]+wk[3]) && (wr[1]+wr[3] >= wk[1])) ) + { + collide = 1; + break; + } + } + + if( !collide ) + break; + else + wr[1] += 18; + } + + ui_text( wr, perf, 1, k_ui_align_middle_left, 0 ); + rect_copy( wr, overlap_buffer[ overlap_length ++ ] ); + } + } +#endif + } + + audio_unlock(); +} diff --git a/vg_audio.h b/vg_audio.h index 0290e85..6272caf 100644 --- a/vg_audio.h +++ b/vg_audio.h @@ -1,41 +1,10 @@ -/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ -#ifndef VG_AUDIO_H -#define VG_AUDIO_H +#pragma once -#define VG_GAME - -#include "vg/vg.h" -#include "vg/vg_stdint.h" -#include "vg/vg_platform.h" -#include "vg/vg_io.h" -#include "vg/vg_m.h" -#include "vg/vg_console.h" -#include "vg/vg_store.h" -#include "vg/vg_profiler.h" -#include "vg/vg_audio_synth_bird.h" - -#ifdef __GNUC__ - #ifndef __clang__ - #pragma GCC push_options - #pragma GCC optimize ("O3") - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - #endif -#endif - -#define STB_VORBIS_MAX_CHANNELS 2 -#include "submodules/stb/stb_vorbis.c" -#undef L -#undef R -#undef C - -#ifdef __GNUC__ - #ifndef __clang__ - #pragma GCC pop_options - #pragma GCC diagnostic pop - #endif -#endif +#include "vg_platform.h" +#include "vg_string.h" +#include "vg_vorbis.h" #define AUDIO_FRAME_SIZE 512 #define AUDIO_MIX_FRAME_SIZE 256 @@ -78,7 +47,8 @@ typedef struct audio_clip audio_clip; typedef struct audio_channel audio_channel; typedef struct audio_lfo audio_lfo; -struct audio_clip{ +struct audio_clip +{ union { /* TODO oof.. */ u64 _p64_; const char *path; @@ -94,10 +64,13 @@ struct audio_clip{ }; }; -struct vg_audio_system{ +struct vg_audio_system +{ SDL_AudioDeviceID sdl_output_device; vg_str device_choice; /* buffer is null? use default from OS */ + bool always_keep_compressed; + void *audio_pool, *decode_buffer; u32 samples_last; @@ -150,9 +123,10 @@ struct vg_audio_system{ pan_movement; union{ - struct synth_bird *bird_handle; - stb_vorbis *vorbis_handle; - }; + struct synth_bird *bird; + stb_vorbis *vorbis; + } + handle; stb_vorbis_alloc vorbis_alloc; @@ -206,1278 +180,48 @@ struct vg_audio_system{ float internal_global_volume, external_global_volume; } -static vg_audio = { .external_global_volume = 1.0f, .dsp_enabled = 1 }; - -#include "vg/vg_audio_dsp.h" - -static struct vg_profile - _vg_prof_audio_decode = {.mode = k_profile_mode_accum, - .name = "[T2] audio_decode()"}, - _vg_prof_audio_mix = {.mode = k_profile_mode_accum, - .name = "[T2] audio_mix()"}, - _vg_prof_dsp = {.mode = k_profile_mode_accum, - .name = "[T2] dsp_process()"}, - vg_prof_audio_decode, - vg_prof_audio_mix, - vg_prof_audio_dsp; - -/* - * These functions are called from the main thread and used to prevent bad - * access. TODO: They should be no-ops in release builds. - */ -static int audio_lock_checker_load(void) -{ - int value; - SDL_AtomicLock( &vg_audio.sl_checker ); - value = vg_audio.sync_locked; - SDL_AtomicUnlock( &vg_audio.sl_checker ); - return value; -} - -static void audio_lock_checker_store( int value ) -{ - SDL_AtomicLock( &vg_audio.sl_checker ); - vg_audio.sync_locked = value; - SDL_AtomicUnlock( &vg_audio.sl_checker ); -} - -static void audio_require_lock(void) -{ - if( audio_lock_checker_load() ) - return; - - vg_error( "Modifying sound effects systems requires locking\n" ); - abort(); -} - -static void audio_lock(void) -{ - SDL_AtomicLock( &vg_audio.sl_sync ); - audio_lock_checker_store(1); -} - -static void audio_unlock(void) -{ - audio_lock_checker_store(0); - SDL_AtomicUnlock( &vg_audio.sl_sync ); -} -static void audio_mixer_callback( void *user, u8 *stream, int frame_count ); - -static void vg_audio_device_init(void){ - SDL_AudioSpec spec_desired, spec_got; - spec_desired.callback = audio_mixer_callback; - spec_desired.channels = 2; - spec_desired.format = AUDIO_F32; - spec_desired.freq = 44100; - spec_desired.padding = 0; - spec_desired.samples = AUDIO_FRAME_SIZE; - spec_desired.silence = 0; - spec_desired.size = 0; - spec_desired.userdata = NULL; - - vg_audio.sdl_output_device = - SDL_OpenAudioDevice( vg_audio.device_choice.buffer, 0, - &spec_desired, &spec_got,0 ); - - vg_info( "Start audio device (%u, F32, %u) @%s\n", - spec_desired.freq, - AUDIO_FRAME_SIZE, - vg_audio.device_choice.buffer ); - - if( vg_audio.sdl_output_device ){ - SDL_PauseAudioDevice( vg_audio.sdl_output_device, 0 ); - vg_success( "Unpaused device %d.\n", vg_audio.sdl_output_device ); - } - else{ - vg_error( - "SDL_OpenAudioDevice failed. Your default audio device must support:\n" - " Frequency: 44100 hz\n" - " Buffer size: 512\n" - " Channels: 2\n" - " Format: s16 or f32\n" ); - } -} - -static void vg_audio_register(void){ - vg_console_reg_var( "debug_audio", &vg_audio.debug_ui, - k_var_dtype_i32, VG_VAR_CHEAT ); - vg_console_reg_var( "debug_dsp", &vg_audio.debug_dsp, - k_var_dtype_i32, VG_VAR_CHEAT ); - vg_console_reg_var( "volume", &vg_audio.external_global_volume, - k_var_dtype_f32, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_audio_device", &vg_audio.device_choice, - k_var_dtype_str, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_dsp", &vg_audio.dsp_enabled, - k_var_dtype_i32, VG_VAR_PERSISTENT ); -} - -static void vg_audio_init(void){ - /* allocate memory */ - /* 32mb fixed */ - vg_audio.audio_pool = - vg_create_linear_allocator( vg_mem.rtmemory, 1024*1024*32, - VG_MEMORY_SYSTEM ); - - /* fixed */ - u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; - vg_audio.decode_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); - - vg_dsp_init(); - vg_audio_device_init(); -} - -static void vg_audio_free(void) -{ - vg_dsp_free(); - SDL_CloseAudioDevice( vg_audio.sdl_output_device ); -} - -/* - * thread 1 - */ - -#define AUDIO_EDIT_VOLUME_SLOPE 0x1 -#define AUDIO_EDIT_VOLUME 0x2 -#define AUDIO_EDIT_LFO_PERIOD 0x4 -#define AUDIO_EDIT_LFO_WAVE 0x8 -#define AUDIO_EDIT_LFO_ATTACHMENT 0x10 -#define AUDIO_EDIT_SPACIAL 0x20 -#define AUDIO_EDIT_OWNERSHIP 0x40 -#define AUDIO_EDIT_SAMPLING_RATE 0x80 - -static void audio_channel_init( audio_channel *ch, audio_clip *clip, - u32 flags ){ - audio_require_lock(); - ch->group = 0; - ch->world_id = 0; - ch->source = clip; - ch->flags = flags; - ch->colour = 0x00333333; - - if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_bird ) - strcpy( ch->name, "[array]" ); - else if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_gen ) - strcpy( ch->name, "[program]" ); - else - vg_strncpy( clip->path, ch->name, 32, k_strncpy_always_add_null ); - - ch->allocated = 1; - - ch->editable_state.relinquished = 0; - ch->editable_state.volume = 1.0f; - ch->editable_state.volume_target = 1.0f; - ch->editable_state.pan = 0.0f; - ch->editable_state.pan_target = 0.0f; - ch->editable_state.volume_rate = 0; - ch->editable_state.pan_rate = 0; - v4_copy((v4f){0.0f,0.0f,0.0f,1.0f},ch->editable_state.spacial_falloff); - ch->editable_state.lfo = NULL; - ch->editable_state.lfo_amount = 0.0f; - ch->editable_state.sampling_rate = 1.0f; - ch->editble_state_write_mask = 0x00; -} - -static void audio_channel_group( audio_channel *ch, u16 group ) -{ - audio_require_lock(); - ch->group = group; - ch->colour = (((u32)group * 29986577) & 0x00ffffff) | 0xff000000; -} - -static void audio_channel_world( audio_channel *ch, u8 world_id ) -{ - audio_require_lock(); - ch->world_id = world_id; -} - -static audio_channel *audio_get_first_idle_channel(void) -{ - audio_require_lock(); - for( int i=0; iallocated ){ - return ch; - } - } - - return NULL; -} - -static audio_channel *audio_get_group_idle_channel( u16 group, u32 max_count ) -{ - audio_require_lock(); - u32 count = 0; - audio_channel *dest = NULL; - - for( int i=0; iallocated ){ - if( ch->group == group ){ - count ++; - } - } - else{ - if( !dest ) - dest = ch; - } - } - - if( dest && (count < max_count) ){ - return dest; - } - - return NULL; -} - -static audio_channel *audio_get_group_first_active_channel( u16 group ) -{ - audio_require_lock(); - for( int i=0; iallocated && (ch->group == group) ) - return ch; - } - return NULL; -} - -static int audio_channel_finished( audio_channel *ch ) -{ - audio_require_lock(); - if( ch->readable_activity == k_channel_activity_end ) - return 1; - else - return 0; -} - -static audio_channel *audio_relinquish_channel( audio_channel *ch ) -{ - audio_require_lock(); - ch->editable_state.relinquished = 1; - ch->editble_state_write_mask |= AUDIO_EDIT_OWNERSHIP; - return NULL; -} - -static void audio_channel_slope_volume( audio_channel *ch, float length, - float new_volume ) -{ - audio_require_lock(); - ch->editable_state.volume_target = new_volume; - ch->editable_state.volume_rate = length * 44100.0f; - ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME_SLOPE; -} - -static void audio_channel_set_sampling_rate( audio_channel *ch, float rate ) -{ - audio_require_lock(); - ch->editable_state.sampling_rate = rate; - ch->editble_state_write_mask |= AUDIO_EDIT_SAMPLING_RATE; -} - -static void audio_channel_edit_volume( audio_channel *ch, - float new_volume, int instant ) -{ - audio_require_lock(); - if( instant ){ - ch->editable_state.volume = new_volume; - ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME; - } - else{ - audio_channel_slope_volume( ch, 0.05f, new_volume ); - } -} - -static audio_channel *audio_channel_fadeout( audio_channel *ch, float length ) -{ - audio_require_lock(); - audio_channel_slope_volume( ch, length, 0.0f ); - return audio_relinquish_channel( ch ); -} - -static void audio_channel_fadein( audio_channel *ch, float length ) -{ - audio_require_lock(); - audio_channel_edit_volume( ch, 0.0f, 1 ); - audio_channel_slope_volume( ch, length, 1.0f ); -} - -static audio_channel *audio_channel_crossfade( audio_channel *ch, - audio_clip *new_clip, - float length, u32 flags ) -{ - audio_require_lock(); - u32 cursor = 0; - - if( ch ) - ch = audio_channel_fadeout( ch, length ); - - audio_channel *replacement = audio_get_first_idle_channel(); - - if( replacement ){ - audio_channel_init( replacement, new_clip, flags ); - audio_channel_fadein( replacement, length ); - } - - return replacement; -} - -static void audio_channel_sidechain_lfo( audio_channel *ch, int lfo_id, - float amount ) -{ - audio_require_lock(); - ch->editable_state.lfo = &vg_audio.oscillators[ lfo_id ]; - ch->editable_state.lfo_amount = amount; - ch->editble_state_write_mask |= AUDIO_EDIT_LFO_ATTACHMENT; -} - -static void audio_channel_set_spacial( audio_channel *ch, v3f co, float range ) -{ - audio_require_lock(); - if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ - v3_copy( co, ch->editable_state.spacial_falloff ); - - if( range == 0.0f ) - ch->editable_state.spacial_falloff[3] = 1.0f; - else - ch->editable_state.spacial_falloff[3] = 1.0f/range; - - ch->editble_state_write_mask |= AUDIO_EDIT_SPACIAL; - } - else{ - vg_warn( "Tried to set spacialization paramaters for 2D channel (%s)\n", - ch->name ); - } -} - -static int audio_oneshot_3d( audio_clip *clip, v3f position, - float range, float volume ) -{ - audio_require_lock(); - audio_channel *ch = audio_get_first_idle_channel(); - - if( ch ){ - audio_channel_init( ch, clip, AUDIO_FLAG_SPACIAL_3D ); - audio_channel_set_spacial( ch, position, range ); - audio_channel_edit_volume( ch, volume, 1 ); - ch = audio_relinquish_channel( ch ); - - return 1; - } - else - return 0; -} - -static int audio_oneshot( audio_clip *clip, float volume, float pan ) -{ - audio_require_lock(); - audio_channel *ch = audio_get_first_idle_channel(); - - if( ch ){ - audio_channel_init( ch, clip, 0x00 ); - audio_channel_edit_volume( ch, volume, 1 ); - ch = audio_relinquish_channel( ch ); - - return 1; - } - else - return 0; -} - -static void audio_set_lfo_wave( int id, enum lfo_wave_type type, - float coefficient ) -{ - audio_require_lock(); - audio_lfo *lfo = &vg_audio.oscillators[ id ]; - lfo->editable_state.polynomial_coefficient = coefficient; - lfo->editable_state.wave_type = type; - - lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_WAVE; -} - -static void audio_set_lfo_frequency( int id, float freq ) -{ - audio_require_lock(); - audio_lfo *lfo = &vg_audio.oscillators[ id ]; - lfo->editable_state.period = 44100.0f / freq; - lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_PERIOD; -} - - -/* - * Committers - * ----------------------------------------------------------------------------- - */ -static int audio_channel_load_source( audio_channel *ch ) -{ - u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; - - if( format == k_audio_format_vorbis ){ - /* Setup vorbis decoder */ - u32 index = ch - vg_audio.channels; - - u8 *buf = (u8*)vg_audio.decode_buffer, - *loc = &buf[AUDIO_DECODE_SIZE*index]; - - stb_vorbis_alloc alloc = { - .alloc_buffer = (char *)loc, - .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE - }; - - int err; - stb_vorbis *decoder = stb_vorbis_open_memory( - ch->source->data, - ch->source->size, &err, &alloc ); - - if( !decoder ){ - vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", - ch->source->path, err ); - return 0; - } - else{ - ch->source_length = stb_vorbis_stream_length_in_samples( decoder ); - ch->vorbis_handle = decoder; - } - } - else if( format == k_audio_format_bird ){ - u32 index = ch - vg_audio.channels; - - u8 *buf = (u8*)vg_audio.decode_buffer; - struct synth_bird *loc = (void *)&buf[AUDIO_DECODE_SIZE*index]; - - memcpy( loc, ch->source->data, ch->source->size ); - synth_bird_reset( loc ); - - ch->bird_handle = loc; - ch->source_length = synth_bird_get_length_in_samples( loc ); - } - else if( format == k_audio_format_stereo ){ - ch->source_length = ch->source->size / 2; - } - else if( format == k_audio_format_gen ){ - ch->source_length = 0xffffffff; - } - else{ - ch->source_length = ch->source->size; - } - - return 1; -} - -static void audio_decode_uncompressed_mono( i16 *src, u32 count, float *dst ) -{ - for( u32 i=0; istereo - */ -static int -stb_vorbis_get_samples_float_interleaved_stereo( stb_vorbis *f, float *buffer, - int len ) -{ - int n = 0, - c = VG_MIN( 1, f->channels - 1 ); - - while( n < len ) { - int k = f->channel_buffer_end - f->channel_buffer_start; - - if( n+k >= len ) - k = len - n; - - for( int j=0; j < k; ++j ) { - *buffer++ = f->channel_buffers[ 0 ][f->channel_buffer_start+j]; - *buffer++ = f->channel_buffers[ c ][f->channel_buffer_start+j]; - } - - n += k; - f->channel_buffer_start += k; - - if( n == len ) - break; - - if( !stb_vorbis_get_frame_float( f, NULL, NULL )) - break; - } - - return n; -} - -/* - * ........ more wrecked code sorry! - */ -static int -stb_vorbis_get_samples_i16_downmixed( stb_vorbis *f, i16 *buffer, int len ) -{ - int n = 0, - c = VG_MIN( 1, f->channels - 1 ); - - while( n < len ) { - int k = f->channel_buffer_end - f->channel_buffer_start; - - if( n+k >= len ) - k = len - n; - - for( int j=0; j < k; ++j ) { - float sl = f->channel_buffers[ 0 ][f->channel_buffer_start+j], - sr = f->channel_buffers[ c ][f->channel_buffer_start+j]; - - *buffer++ = vg_clampf( 0.5f*(sl+sr), -1.0f, 1.0f ) * 32767.0f; - //*buffer++ = vg_clampf( sr, -1.0f, 1.0f ) * 32767.0f; - } - - n += k; - f->channel_buffer_start += k; - - if( n == len ) - break; - - if( !stb_vorbis_get_frame_float( f, NULL, NULL )) - break; - } - - return n; -} - -static inline float audio_lfo_pull_sample( audio_lfo *lfo ) -{ - lfo->time ++; - - if( lfo->time >= lfo->_.period ) - lfo->time = 0; - - float t = lfo->time; - t /= (float)lfo->_.period; - - if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ - /* - * # - * # # - * # # - * # # - * ### # ### - * ## # - * # # - * # # - * ## - */ - - t *= 2.0f; - t -= 1.0f; - - return (( 2.0f * lfo->sqrt_polynomial_coefficient * t ) / - /* --------------------------------------- */ - ( 1.0f + lfo->_.polynomial_coefficient * t*t ) - - ) * (1.0f-fabsf(t)); - } - else{ - return 0.0f; - } -} - -static void audio_channel_get_samples( audio_channel *ch, - u32 count, float *buf ) -{ - vg_profile_begin( &_vg_prof_audio_decode ); - - u32 remaining = count; - u32 buffer_pos = 0; - - u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; - - while( remaining ){ - u32 samples_this_run = VG_MIN(remaining, ch->source_length - ch->cursor); - remaining -= samples_this_run; - - float *dst = &buf[ buffer_pos * 2 ]; - - if( format == k_audio_format_stereo ){ - for( int i=0;ivorbis_handle, - dst, - samples_this_run ); - - if( read_samples != samples_this_run ){ - vg_warn( "Invalid samples read (%s)\n", ch->source->path ); - - for( int i=0; ibird_handle, dst, samples_this_run ); - } - else if( format == k_audio_format_gen ){ - void (*fn)( void *data, f32 *buf, u32 count ) = ch->source->func; - fn( ch->source->data, dst, samples_this_run ); - } - else{ - i16 *src_buffer = ch->source->data, - *src = &src_buffer[ch->cursor]; - - audio_decode_uncompressed_mono( src, samples_this_run, dst ); - } - - ch->cursor += samples_this_run; - buffer_pos += samples_this_run; - - if( (ch->flags & AUDIO_FLAG_LOOP) && remaining ){ - if( format == k_audio_format_vorbis ) - stb_vorbis_seek_start( ch->vorbis_handle ); - else if( format == k_audio_format_bird ) - synth_bird_reset( ch->bird_handle ); - - ch->cursor = 0; - continue; - } - else - break; - } - - while( remaining ){ - buf[ buffer_pos*2 + 0 ] = 0.0f; - buf[ buffer_pos*2 + 1 ] = 0.0f; - buffer_pos ++; - - remaining --; - } - - vg_profile_end( &_vg_prof_audio_decode ); -} - -static void audio_channel_mix( audio_channel *ch, float *buffer ) -{ - float framevol_l = vg_audio.internal_global_volume, - framevol_r = vg_audio.internal_global_volume; - - float frame_samplerate = ch->_.sampling_rate; - - if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ - v3f delta; - v3_sub( ch->_.spacial_falloff, vg_audio.internal_listener_pos, delta ); - - float dist = v3_length( delta ), - vol = vg_maxf( 0.0f, 1.0f - ch->_.spacial_falloff[3]*dist ); - - if( dist <= 0.01f ){ - - } - else{ - v3_muls( delta, 1.0f/dist, delta ); - float pan = v3_dot( vg_audio.internal_listener_ears, delta ); - vol = powf( vol, 5.0f ); - - framevol_l *= (vol * 0.5f) * (1.0f - pan); - framevol_r *= (vol * 0.5f) * (1.0f + pan); - - if( !(ch->source->flags & AUDIO_FLAG_NO_DOPPLER) ){ - const float vs = 323.0f; - - float dv = v3_dot(delta,vg_audio.internal_listener_velocity); - float doppler = (vs+dv)/vs; - doppler = vg_clampf( doppler, 0.6f, 1.4f ); - - if( fabsf(doppler-1.0f) > 0.01f ) - frame_samplerate *= doppler; - } - } - - if( !vg_validf( framevol_l ) || - !vg_validf( framevol_r ) || - !vg_validf( frame_samplerate ) ){ - vg_fatal_error( "Invalid sampling conditions.\n" - "This crash is to protect your ears.\n" - " channel: %p (%s)\n" - " sample_rate: %f\n" - " volume: L%f R%f\n" - " listener: %.2f %.2f %.2f [%.2f %.2f %.2f]\n", - ch, ch->name, frame_samplerate, - framevol_l, framevol_r, - vg_audio.internal_listener_pos[0], - vg_audio.internal_listener_pos[1], - vg_audio.internal_listener_pos[2], - vg_audio.internal_listener_ears[0], - vg_audio.internal_listener_ears[1], - vg_audio.internal_listener_ears[2] - ); - } - } - - u32 buffer_length = AUDIO_MIX_FRAME_SIZE; - if( frame_samplerate != 1.0f ){ - float l = ceilf( (float)(AUDIO_MIX_FRAME_SIZE) * frame_samplerate ); - buffer_length = l+1; - } - - float pcf[ AUDIO_MIX_FRAME_SIZE * 2 * 2 ]; - - audio_channel_get_samples( ch, buffer_length, pcf ); - - vg_profile_begin( &_vg_prof_audio_mix ); - - float volume_movement = ch->volume_movement; - float const fvolume_rate = vg_maxf( 1.0f, ch->_.volume_rate ); - const float inv_volume_rate = 1.0f/fvolume_rate; - - float volume = ch->_.volume; - const float volume_start = ch->volume_movement_start; - const float volume_target = ch->_.volume_target; - - for( u32 j=0; j_.lfo ) - vol_norm *= 1.0f + audio_lfo_pull_sample(ch->_.lfo) * ch->_.lfo_amount; - - float vol_l = vol_norm * framevol_l, - vol_r = vol_norm * framevol_r, - sample_l, - sample_r; - - if( frame_samplerate != 1.0f ){ - /* absolutely garbage resampling, but it will do - */ - - float sample_index = frame_samplerate * (float)j; - float t = vg_fractf( sample_index ); - - u32 i0 = floorf( sample_index ), - i1 = i0+1; - - sample_l = pcf[ i0*2+0 ]*(1.0f-t) + pcf[ i1*2+0 ]*t; - sample_r = pcf[ i0*2+1 ]*(1.0f-t) + pcf[ i1*2+1 ]*t; - } - else{ - sample_l = pcf[ j*2+0 ]; - sample_r = pcf[ j*2+1 ]; - } - - buffer[ j*2+0 ] += sample_l * vol_l; - buffer[ j*2+1 ] += sample_r * vol_r; - } - - ch->volume_movement += AUDIO_MIX_FRAME_SIZE; - ch->volume_movement = VG_MIN( ch->volume_movement, ch->_.volume_rate ); - ch->_.volume = volume; - - vg_profile_end( &_vg_prof_audio_mix ); -} - -static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ - /* - * Copy data and move edit flags to commit flags - * ------------------------------------------------------------- */ - audio_lock(); - int use_dsp = vg_audio.dsp_enabled; - - v3_copy( vg_audio.external_listener_pos, vg_audio.internal_listener_pos ); - v3_copy( vg_audio.external_listener_ears, vg_audio.internal_listener_ears ); - v3_copy( vg_audio.external_lister_velocity, - vg_audio.internal_listener_velocity ); - vg_audio.internal_global_volume = vg_audio.external_global_volume; - - for( int i=0; iallocated ) - continue; - - if( ch->activity == k_channel_activity_alive ){ - if( (ch->cursor >= ch->source_length) && - !(ch->flags & AUDIO_FLAG_LOOP) ) - { - ch->activity = k_channel_activity_end; - } - } - - /* process relinquishments */ - if( (ch->activity != k_channel_activity_reset) && ch->_.relinquished ){ - if( (ch->activity == k_channel_activity_end) - || (ch->_.volume == 0.0f) - || (ch->activity == k_channel_activity_error) ) - { - ch->_.relinquished = 0; - ch->allocated = 0; - ch->activity = k_channel_activity_reset; - continue; - } - } - - /* process new channels */ - if( ch->activity == k_channel_activity_reset ){ - ch->_ = ch->editable_state; - ch->cursor = 0; - ch->source_length = 0; - ch->activity = k_channel_activity_wake; - } - - if( ch->editble_state_write_mask & AUDIO_EDIT_OWNERSHIP ) - ch->_.relinquished = ch->editable_state.relinquished; - else - ch->editable_state.relinquished = ch->_.relinquished; - - - if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME ){ - ch->_.volume = ch->editable_state.volume; - ch->_.volume_target = ch->editable_state.volume; - } - else{ - ch->editable_state.volume = ch->_.volume; - } - - - if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME_SLOPE ){ - ch->volume_movement_start = ch->_.volume; - ch->volume_movement = 0; - - ch->_.volume_target = ch->editable_state.volume_target; - ch->_.volume_rate = ch->editable_state.volume_rate; - } - else{ - ch->editable_state.volume_target = ch->_.volume_target; - ch->editable_state.volume_rate = ch->_.volume_rate; - } - - - if( ch->editble_state_write_mask & AUDIO_EDIT_SAMPLING_RATE ) - ch->_.sampling_rate = ch->editable_state.sampling_rate; - else - ch->editable_state.sampling_rate = ch->_.sampling_rate; - - - if( ch->editble_state_write_mask & AUDIO_EDIT_LFO_ATTACHMENT ){ - ch->_.lfo = ch->editable_state.lfo; - ch->_.lfo_amount = ch->editable_state.lfo_amount; - } - else{ - ch->editable_state.lfo = ch->_.lfo; - ch->editable_state.lfo_amount = ch->_.lfo_amount; - } - - - if( ch->editble_state_write_mask & AUDIO_EDIT_SPACIAL ) - v4_copy( ch->editable_state.spacial_falloff,ch->_.spacial_falloff ); - else - v4_copy( ch->_.spacial_falloff,ch->editable_state.spacial_falloff ); - - - /* currently readonly, i guess */ - ch->editable_state.pan_target = ch->_.pan_target; - ch->editable_state.pan = ch->_.pan; - ch->editble_state_write_mask = 0x00; - } - - for( int i=0; ieditble_state_write_mask & AUDIO_EDIT_LFO_WAVE ){ - lfo->_.wave_type = lfo->editable_state.wave_type; - - if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ - lfo->_.polynomial_coefficient = - lfo->editable_state.polynomial_coefficient; - lfo->sqrt_polynomial_coefficient = - sqrtf(lfo->_.polynomial_coefficient); - } - } - - if( lfo->editble_state_write_mask & AUDIO_EDIT_LFO_PERIOD ){ - if( lfo->_.period ){ - float t = lfo->time; - t/= (float)lfo->_.period; - - lfo->_.period = lfo->editable_state.period; - lfo->time = lfo->_.period * t; - } - else{ - lfo->time = 0; - lfo->_.period = lfo->editable_state.period; - } - } - - lfo->editble_state_write_mask = 0x00; - } - - dsp_update_tunings(); - audio_unlock(); - - /* - * Process spawns - * ------------------------------------------------------------- */ - for( int i=0; iactivity == k_channel_activity_wake ){ - if( audio_channel_load_source( ch ) ) - ch->activity = k_channel_activity_alive; - else - ch->activity = k_channel_activity_error; - } - } - - /* - * Mix everything - * -------------------------------------------------------- */ - int frame_count = byte_count/(2*sizeof(float)); - - /* Clear buffer */ - float *pOut32F = (float *)stream; - for( int i=0; itime_startframe = lfo->time; - } - - for( int i=0; iactivity == k_channel_activity_alive ){ - if( ch->_.lfo ) - ch->_.lfo->time = ch->_.lfo->time_startframe; - - u32 remaining = frame_count, - subpos = 0; - - while( remaining ){ - audio_channel_mix( ch, pOut32F+subpos ); - remaining -= AUDIO_MIX_FRAME_SIZE; - subpos += AUDIO_MIX_FRAME_SIZE*2; - } - } - } - - if( use_dsp ){ - vg_profile_begin( &_vg_prof_dsp ); - for( int i=0; ireadable_activity = ch->activity; - } - - /* Profiling information - * ----------------------------------------------- */ - vg_profile_increment( &_vg_prof_audio_decode ); - vg_profile_increment( &_vg_prof_audio_mix ); - vg_profile_increment( &_vg_prof_dsp ); - - vg_prof_audio_mix = _vg_prof_audio_mix; - vg_prof_audio_decode = _vg_prof_audio_decode; - vg_prof_audio_dsp = _vg_prof_dsp; - - vg_audio.samples_last = frame_count; - - if( vg_audio.debug_dsp ){ - vg_dsp_update_texture(); - } - - audio_unlock(); -} - -static void audio_clip_load( audio_clip *clip, void *lin_alloc ) -{ - if( lin_alloc == NULL ) - lin_alloc = vg_audio.audio_pool; - -#ifdef VG_AUDIO_FORCE_COMPRESSED - - if( (clip->flags & AUDIO_FLAG_FORMAT) != k_audio_format_bird ){ - clip->flags &= ~AUDIO_FLAG_FORMAT; - clip->flags |= k_audio_format_vorbis; - } - -#endif - - /* load in directly */ - u32 format = clip->flags & AUDIO_FLAG_FORMAT; - - /* TODO: This contains audio_lock() and unlock, but i don't know why - * can probably remove them. Low priority to check this */ - - /* TODO: packed files for vorbis etc, should take from data if its not not - * NULL when we get the clip - */ - - if( format == k_audio_format_vorbis ){ - if( !clip->path ){ - vg_fatal_error( "No path specified, embeded vorbis unsupported" ); - } - - audio_lock(); - clip->data = vg_file_read( lin_alloc, clip->path, &clip->size ); - audio_unlock(); - - if( !clip->data ) - vg_fatal_error( "Audio failed to load" ); - - float mb = (float)(clip->size) / (1024.0f*1024.0f); - vg_info( "Loaded audio clip '%s' (%.1fmb)\n", clip->path, mb ); - } - else if( format == k_audio_format_stereo ){ - vg_fatal_error( "Unsupported format (Stereo uncompressed)" ); - } - else if( format == k_audio_format_bird ){ - if( !clip->data ){ - vg_fatal_error( "No data, external birdsynth unsupported" ); - } - - u32 total_size = clip->size + sizeof(struct synth_bird); - total_size -= sizeof(struct synth_bird_settings); - total_size = vg_align8( total_size ); - - if( total_size > AUDIO_DECODE_SIZE ) - vg_fatal_error( "Bird coding too long\n" ); - - struct synth_bird *bird = vg_linear_alloc( lin_alloc, total_size ); - memcpy( &bird->settings, clip->data, clip->size ); - - clip->data = bird; - clip->size = total_size; - - vg_info( "Loaded bird synthesis pattern (%u bytes)\n", total_size ); - } - else{ - if( !clip->path ){ - vg_fatal_error( "No path specified, embeded mono unsupported" ); - } - - vg_linear_clear( vg_mem.scratch ); - u32 fsize; - - stb_vorbis_alloc alloc = { - .alloc_buffer = vg_linear_alloc( vg_mem.scratch, AUDIO_DECODE_SIZE ), - .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE - }; - - void *filedata = vg_file_read( vg_mem.scratch, clip->path, &fsize ); - - int err; - stb_vorbis *decoder = stb_vorbis_open_memory( - filedata, fsize, &err, &alloc ); - - if( !decoder ){ - vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", - clip->path, err ); - vg_fatal_error( "Vorbis decode error" ); - } - - /* only mono is supported in uncompressed */ - u32 length_samples = stb_vorbis_stream_length_in_samples( decoder ), - data_size = length_samples * sizeof(i16); - - audio_lock(); - clip->data = vg_linear_alloc( lin_alloc, vg_align8(data_size) ); - clip->size = length_samples; - audio_unlock(); - - int read_samples = stb_vorbis_get_samples_i16_downmixed( - decoder, clip->data, length_samples ); - - if( read_samples != length_samples ) - vg_fatal_error( "Decode error" ); - -#if 0 - float mb = (float)(data_size) / (1024.0f*1024.0f); - vg_info( "Loaded audio clip '%s' (%.1fmb) %u samples\n", clip->path, mb, - length_samples ); -#endif - } -} - -static void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) -{ - for( int i=0; idata && clip->size ) - return; - - audio_unlock(); - vg_fatal_error( "Must load audio clip before playing! \n" ); -} - -/* - * Debugging - */ - -static void audio_debug_ui( +extern vg_audio; + +void audio_clip_load( audio_clip *clip, void *lin_alloc ); +void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ); + +void vg_audio_register(void); +void vg_audio_device_init(void); +void vg_audio_init(void); +void vg_audio_free(void); + +void audio_lock(void); +void audio_unlock(void); + +void audio_channel_init( audio_channel *ch, audio_clip *clip, u32 flags ); +void audio_channel_group( audio_channel *ch, u16 group ); +void audio_channel_world( audio_channel *ch, u8 world_id ); +audio_channel *audio_get_first_idle_channel(void); +audio_channel *audio_get_group_idle_channel( u16 group, u32 max_count ); +audio_channel *audio_get_group_first_active_channel( u16 group ); +int audio_channel_finished( audio_channel *ch ); +audio_channel *audio_relinquish_channel( audio_channel *ch ); +void audio_channel_slope_volume( audio_channel *ch, f32 length, f32 new_vol ); +void audio_channel_set_sampling_rate( audio_channel *ch, float rate ); +void audio_channel_edit_volume( audio_channel *ch, f32 new_vol, int instant ); +audio_channel *audio_channel_fadeout( audio_channel *ch, float length ); +void audio_channel_fadein( audio_channel *ch, float length ); +audio_channel *audio_channel_crossfade( audio_channel *ch, + audio_clip *new_clip, + float length, u32 flags ); +void audio_channel_sidechain_lfo( audio_channel *ch, int lfo_id, f32 amount ); +void audio_channel_set_spacial( audio_channel *ch, v3f co, float range ); +int audio_oneshot_3d( audio_clip *clip, v3f position, f32 range, f32 volume ); +int audio_oneshot( audio_clip *clip, f32 volume, f32 pan ); +void audio_set_lfo_wave( int id, enum lfo_wave_type type, f32 coefficient ); +void audio_set_lfo_frequency( int id, float freq ); +int audio_channel_load_source( audio_channel *ch ); + +void audio_debug_ui( #ifdef VG_3D m4x4f #else m3x3f #endif - mtx_pv ){ - - if( !vg_audio.debug_ui ) - return; - - audio_lock(); - - glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); - glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 256, 256, - GL_RGBA, GL_UNSIGNED_BYTE, - vg_dsp.view_texture_buffer ); - - /* - * Profiler - * ----------------------------------------------------------------------- - */ - - float budget = ((double)vg_audio.samples_last / 44100.0) * 1000.0; - vg_profile_drawn( (struct vg_profile *[]){ &vg_prof_audio_decode, - &vg_prof_audio_mix, - &vg_prof_audio_dsp}, 3, - budget, (ui_rect){ 4, VG_PROFILE_SAMPLE_COUNT*2 + 8, - 512, 0 }, 0, 0 ); - - - char perf[128]; - - /* Draw UI */ - ui_rect window = { - 0, - 0, - 800, - AUDIO_CHANNELS * 18 - }; - - if( vg_audio.debug_dsp ){ - ui_rect view_thing = { 4, vg.window_y-512-4, 512, 512 }; - ui_image( view_thing, vg_dsp.view_texture ); - } - - ui_rect overlap_buffer[ AUDIO_CHANNELS ]; - u32 overlap_length = 0; - - /* Draw audio stack */ - for( int i=0; iallocated ){ - ui_fill( row, 0x50333333 ); - continue; - } - - const char *formats[] = - { - " mono ", - " stereo ", - " vorbis ", - " none0 ", - " none1 ", - " none2 ", - " none3 ", - " none4 ", - "synth:bird", - " none5 ", - " none6 ", - " none7 ", - " none8 ", - " none9 ", - " none10 ", - " none11 ", - }; - - const char *activties[] = - { - "reset", - "wake ", - "alive", - "end ", - "error" - }; - - u32 format_index = (ch->source->flags & AUDIO_FLAG_FORMAT)>>9; - - snprintf( perf, 127, "%02d[%#04x.%#06x]%c%c%cD %s [%s] %4.2fv'%s'", - i, - ch->world_id, ch->group, - (ch->editable_state.relinquished)? 'r': '_', - 0? 'r': '_', - 0? '3': '2', - formats[format_index], - activties[ch->readable_activity], - ch->editable_state.volume, - ch->name ); - - ui_fill( row, 0xa0000000 | ch->colour ); - ui_text( row, perf, 1, k_ui_align_middle_left, 0 ); - -#ifdef VG_3D - if( AUDIO_FLAG_SPACIAL_3D ){ - v4f wpos; - v3_copy( ch->editable_state.spacial_falloff, wpos ); - - wpos[3] = 1.0f; - m4x4_mulv( mtx_pv, wpos, wpos ); - - if( wpos[3] > 0.0f ){ - v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos ); - v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos ); - - ui_rect wr; - wr[0] = vg_clampf(wpos[0] * vg.window_x, -32000.0f,32000.0f); - wr[1] = vg_clampf((1.0f-wpos[1]) * vg.window_y,-32000.0f,32000.0f); - wr[2] = 1000; - wr[3] = 17; - - for( int j=0; j<12; j++ ){ - int collide = 0; - for( int k=0; k= wk[0])) && - ((wr[1] <= wk[1]+wk[3]) && (wr[1]+wr[3] >= wk[1])) ) - { - collide = 1; - break; - } - } - - if( !collide ) - break; - else - wr[1] += 18; - } - - ui_text( wr, perf, 1, k_ui_align_middle_left, 0 ); - rect_copy( wr, overlap_buffer[ overlap_length ++ ] ); - } - } -#endif - } - - audio_unlock(); -} - -#endif /* VG_AUDIO_H */ + mtx_pv ); diff --git a/vg_audio_dsp.c b/vg_audio_dsp.c new file mode 100644 index 0000000..3714e32 --- /dev/null +++ b/vg_audio_dsp.c @@ -0,0 +1,298 @@ +#include "vg_audio_dsp.h" +#include "vg_mem.h" +#include "vg_async.h" + +struct vg_dsp vg_dsp; + +float *dsp_allocate( u32 samples ) +{ + samples = vg_align4( samples ); + + if( vg_dsp.allocations + samples > (1024*1024)/4 ) + vg_fatal_error( "too much dsp" ); + + float *buf = &vg_dsp.buffer[ vg_dsp.allocations ]; + vg_dsp.allocations += samples; + + return buf; +} + + +/* + * filters + * ---------------------------------------------- + */ + +f32 dsp_biquad_process( struct dsp_biquad *bq, f32 xn ){ + f32 yn = + bq->a0*xn + bq->a1*bq->xnz1 + bq->a2*bq->xnz2 + - bq->b1*bq->ynz1 - bq->b2*bq->ynz2; + bq->xnz2 = bq->xnz1; + bq->xnz1 = xn; + bq->ynz2 = bq->ynz1; + bq->ynz1 = yn; + return yn + bq->offset; +} + +void dsp_init_biquad_butterworth_lpf( struct dsp_biquad *bq, f32 fc ){ + f32 c = 1.0f/tanf(VG_PIf*fc / 44100.0f); + bq->a0 = 1.0f / (1.0f + sqrtf(2.0f)*c + powf(c, 2.0f) ); + bq->a1 = 2.0f * bq->a0; + bq->a2 = bq->a0; + bq->b1 = 2.0f * bq->a0*(1.0f - powf(c, 2.0f)); + bq->b2 = bq->a0 * (1.0f - sqrtf(2.0f)*c + powf(c, 2.0f) ); +} + +void dsp_read_delay( struct dsp_delay *delay, float *s, u32 t ){ + u32 index = delay->cur+t; + + if( index >= delay->length ) + index -= delay->length; + + *s = delay->buffer[ index ]; +} + +void dsp_write_delay( struct dsp_delay *delay, float *s ) +{ + u32 index = delay->cur; + delay->buffer[ index ] = *s; + + delay->cur ++; + + if( delay->cur >= delay->length ) + delay->cur = 0; +} + +void dsp_init_delay( struct dsp_delay *delay, float length ) +{ + delay->length = 44100.0f * length; + delay->cur = 0; + delay->buffer = dsp_allocate( delay->length ); + + for( int i=0; ilength; i++ ) + delay->buffer[i] = 0.0f; +} + +void dsp_update_lpf( struct dsp_lpf *lpf, float freq ) +{ + lpf->exponent = 1.0f-expf( -(1.0f/44100.0f) * 2.0f * VG_PIf * freq ); +} + +void dsp_init_lpf( struct dsp_lpf *lpf, float freq ) +{ + lpf->buffer = dsp_allocate( 4 ); + lpf->buffer[0] = 0.0f; + dsp_update_lpf( lpf, freq ); +} + +void dsp_write_lpf( struct dsp_lpf *lpf, float *s ) +{ + float diff = *s - lpf->buffer[0]; + lpf->buffer[0] += diff * lpf->exponent; +} + +void dsp_read_lpf( struct dsp_lpf *lpf, float *s ) +{ + *s = lpf->buffer[0]; +} + +void dsp_init_schroeder( struct dsp_schroeder *sch, float length, float gain ) +{ + dsp_init_delay( &sch->M, length ); + sch->gain = gain; +} + +void dsp_process_schroeder( struct dsp_schroeder *sch, + float *input, float *output ) +{ + float dry = *input; + + float delay_output; + dsp_read_delay( &sch->M, &delay_output, 1 ); + + float feedback_attenuated = delay_output * sch->gain, + input_feedback_sum = dry + feedback_attenuated; + + dsp_write_delay( &sch->M, &input_feedback_sum ); + + *output = delay_output - input_feedback_sum*sch->gain; +} + +/* temporary global design */ +static struct dsp_lpf __lpf_mud_free; +static struct dsp_delay __echos[8]; + +#ifdef VG_ECHO_LPF_BUTTERWORTH +static struct dsp_biquad __echos_lpf[8]; +#else +static struct dsp_lpf __echos_lpf[8]; +#endif +static struct dsp_schroeder __diffusion_chain[8]; + +static void async_vg_dsp_alloc_texture( void *payload, u32 size ) +{ + glGenTextures( 1, &vg_dsp.view_texture ); + glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, + GL_RGBA, GL_UNSIGNED_BYTE, vg_dsp.view_texture_buffer ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); +} + +void vg_dsp_init( void ){ + vg_rand_seed( &vg_dsp.rand, 461 ); + vg_dsp.buffer = vg_linear_alloc( vg_mem.rtmemory, 1024*1024*1 ); + vg_dsp.view_texture_buffer = vg_linear_alloc( vg_mem.rtmemory, 512*512 ); + + vg_async_call( async_vg_dsp_alloc_texture, NULL, 0 ); + + /* temporary global design */ + dsp_init_lpf( &__lpf_mud_free, 125.0f ); + + float sizes[] = + { 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f }; + + float variance = 0.1f; + + for( int i=0; i<8; i++ ){ + float reflection_time = ((sizes[i])/343.0f) * 1000.0f; + + float var = 1.0f + (vg_randf64(&vg_dsp.rand)*2.0f - 1.0f) * variance, + total = reflection_time * var; + + dsp_init_delay( &__echos[i], total / 1000.0f ); + + float freq = vg_lerpf( 800.0f, 350.0f, sizes[i] / 256.0f ); + +#ifdef VG_ECHO_LPF_BUTTERWORTH + dsp_init_biquad_butterworth_lpf( &__echos_lpf[i], freq ); +#else + dsp_init_lpf( &__echos_lpf[i], freq ); +#endif + } + + float diffusions[] = { 187.0f, 159.0f, 143.0f, 121.0f, + 79.0f, 57.0f, 27.0f, 11.0f }; + + for( int i=0; i<8; i++ ){ + dsp_init_schroeder( __diffusion_chain+i, diffusions[i]/1000.0f, 0.7f ); + } +} + +void vg_dsp_process( float *stereo_in, float *stereo_out ) +{ + float in_total = (stereo_in[0]+stereo_in[1])*0.5f; + float recieved = 0.0f; + + for( int i=0; i<8; i++ ){ + f32 echo; + dsp_read_delay( __echos+i, &echo, 1 ); + +#ifdef VG_ECHO_LPF_BUTTERWORTH + echo = dsp_biquad_process( __echos_lpf+i, echo ); +#else + dsp_write_lpf( __echos_lpf+i, &echo ); + dsp_read_lpf( __echos_lpf+i, &echo ); +#endif + + recieved += echo * vg_dsp.echo_tunings[i]*0.98; + } + + float diffused = recieved; + + for( int i=0; i<8; i++ ){ + dsp_process_schroeder( __diffusion_chain+i, &diffused, &diffused ); + } + + float diffuse_mix = vg_dsp.reverb_wet_mix; + diffuse_mix = vg_lerpf( recieved, diffused, diffuse_mix ); + float total = in_total + diffuse_mix; + + float low_mud; + dsp_write_lpf( &__lpf_mud_free, &total ); + dsp_read_lpf( &__lpf_mud_free, &low_mud ); + + total -= low_mud; + + for( int i=0; i<8; i++ ) + dsp_write_delay( __echos+i, &total ); + + stereo_out[0] = stereo_in[0]*vg_dsp.reverb_dry_mix; + stereo_out[1] = stereo_in[1]*vg_dsp.reverb_dry_mix; + stereo_out[0] += diffuse_mix*2.0f*vg_dsp.reverb_wet_mix; + stereo_out[1] += diffuse_mix*2.0f*vg_dsp.reverb_wet_mix; +} + +void dsp_update_tunings(void) +{ + float sizes[] = + { 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f }; + float volumes[] = + { 0.2f, 0.3f, 0.5f, 0.7f, 0.8f, 0.9f, 1.0f, 1.0f }; + + float avg_distance = 0.0f; + + for( int i=0; i<8; i++ ) + vg_dsp.echo_tunings[i] = 0.5f; + + for( int j=0; j<14; j++ ){ + float d = vg_dsp.echo_distances[j]; + + for( int i=0; i<7; i++ ){ + if( d < sizes[i+1] ){ + float range = sizes[i+1]-sizes[i]; + float t = vg_clampf( (d - sizes[i])/range, 0.0f, 1.0f ); + + vg_dsp.echo_tunings[i ] += 1.0f-t; + vg_dsp.echo_tunings[i+1] += t; + + break; + } + } + + avg_distance += d; + } + avg_distance /= 14.0f; + + + vg_dsp.reverb_wet_mix =1.0f-vg_clampf((avg_distance-30.0f)/200.0f,0.0f,1.0f); + vg_dsp.reverb_dry_mix =1.0f-vg_dsp.reverb_wet_mix*0.4f; + + float total = 0.0f; + for( int i=0; i<8; i++ ) + total += vg_dsp.echo_tunings[i]; + + if( total > 0.0f ){ + float inverse = 1.0f/total; + + for( int i=0;i<8; i++ ){ + vg_dsp.echo_tunings[i] *= inverse; + } + } + + for( int i=0; i<8; i++ ){ + float freq = vg_lerpf( 200.0f, 500.0f, vg_dsp.echo_tunings[i] ); + +#ifdef VG_ECHO_LPF_BUTTERWORTH + dsp_init_biquad_butterworth_lpf( &__echos_lpf[i], freq ); +#else + dsp_update_lpf( &__echos_lpf[i], freq ); +#endif + } + + for( int i=0;i<8; i++ ){ + vg_dsp.echo_tunings[i] *= volumes[i]; + } +} + +void vg_dsp_free( void ) +{ + glDeleteTextures( 1, &vg_dsp.view_texture ); +} + +void vg_dsp_update_texture( void ) +{ + for( int i=0; i<512*512; i++ ){ + float v = vg_clampf( vg_dsp.buffer[i] * 0.5f + 0.5f, 0.0f, 1.0f ); + vg_dsp.view_texture_buffer[i] = v * 255.0f; + } +} diff --git a/vg_audio_dsp.h b/vg_audio_dsp.h index c2bed10..d74bc02 100644 --- a/vg_audio_dsp.h +++ b/vg_audio_dsp.h @@ -1,45 +1,27 @@ -#ifndef VG_AUDIO_DSP_H -#define VG_AUDIO_DSP_H - -#define VG_GAME -#include "vg/vg.h" +#pragma once //#define VG_ECHO_LPF_BUTTERWORTH -static struct vg_dsp{ - float *buffer; - u32 allocations; - - u8 *view_texture_buffer; - GLuint view_texture; +#include "vg_platform.h" +#include "dep/glad/glad.h" +#include "vg_m.h" - float echo_distances[14], - echo_tunings[8], - reverb_wet_mix, - reverb_dry_mix; - - vg_rand rand; -} -vg_dsp; - -static float *dsp_allocate( u32 samples ) +struct vg_dsp { - samples = vg_align4( samples ); + float *buffer; + u32 allocations; - if( vg_dsp.allocations + samples > (1024*1024)/4 ) - vg_fatal_error( "too much dsp" ); + u8 *view_texture_buffer; + GLuint view_texture; - float *buf = &vg_dsp.buffer[ vg_dsp.allocations ]; - vg_dsp.allocations += samples; + float echo_distances[14], + echo_tunings[8], + reverb_wet_mix, + reverb_dry_mix; - return buf; + vg_rand rand; } - - -/* - * filters - * ---------------------------------------------- - */ +extern vg_dsp; struct dsp_delay { @@ -59,284 +41,27 @@ struct dsp_schroeder float gain; }; -struct dsp_biquad { +struct dsp_biquad +{ f32 a0, a1, a2, b1, b2, c0, d0, xnz1, xnz2, ynz1, ynz2, offset; }; -static f32 dsp_biquad_process( struct dsp_biquad *bq, f32 xn ){ - f32 yn = + bq->a0*xn + bq->a1*bq->xnz1 + bq->a2*bq->xnz2 - - bq->b1*bq->ynz1 - bq->b2*bq->ynz2; - bq->xnz2 = bq->xnz1; - bq->xnz1 = xn; - bq->ynz2 = bq->ynz1; - bq->ynz1 = yn; - return yn + bq->offset; -} - -static void dsp_init_biquad_butterworth_lpf( struct dsp_biquad *bq, f32 fc ){ - f32 c = 1.0f/tanf(VG_PIf*fc / 44100.0f); - bq->a0 = 1.0f / (1.0f + sqrtf(2.0f)*c + powf(c, 2.0f) ); - bq->a1 = 2.0f * bq->a0; - bq->a2 = bq->a0; - bq->b1 = 2.0f * bq->a0*(1.0f - powf(c, 2.0f)); - bq->b2 = bq->a0 * (1.0f - sqrtf(2.0f)*c + powf(c, 2.0f) ); -} - -static inline void dsp_read_delay( struct dsp_delay *delay, float *s, u32 t ){ - u32 index = delay->cur+t; - - if( index >= delay->length ) - index -= delay->length; - - *s = delay->buffer[ index ]; -} - -static inline void dsp_write_delay( struct dsp_delay *delay, float *s ) -{ - u32 index = delay->cur; - delay->buffer[ index ] = *s; - - delay->cur ++; - - if( delay->cur >= delay->length ) - delay->cur = 0; -} - -static void dsp_init_delay( struct dsp_delay *delay, float length ) -{ - delay->length = 44100.0f * length; - delay->cur = 0; - delay->buffer = dsp_allocate( delay->length ); - - for( int i=0; ilength; i++ ) - delay->buffer[i] = 0.0f; -} - -static void dsp_update_lpf( struct dsp_lpf *lpf, float freq ) -{ - lpf->exponent = 1.0f-expf( -(1.0f/44100.0f) * 2.0f * VG_PIf * freq ); -} - -static void dsp_init_lpf( struct dsp_lpf *lpf, float freq ) -{ - lpf->buffer = dsp_allocate( 4 ); - lpf->buffer[0] = 0.0f; - dsp_update_lpf( lpf, freq ); -} - -static inline void dsp_write_lpf( struct dsp_lpf *lpf, float *s ) -{ - float diff = *s - lpf->buffer[0]; - lpf->buffer[0] += diff * lpf->exponent; -} - -static inline void dsp_read_lpf( struct dsp_lpf *lpf, float *s ) -{ - *s = lpf->buffer[0]; -} - -static void dsp_init_schroeder( struct dsp_schroeder *sch, float length, - float gain ) -{ - dsp_init_delay( &sch->M, length ); - sch->gain = gain; -} - -static inline void dsp_process_schroeder( struct dsp_schroeder *sch, - float *input, float *output ) -{ - float dry = *input; - - float delay_output; - dsp_read_delay( &sch->M, &delay_output, 1 ); - - float feedback_attenuated = delay_output * sch->gain, - input_feedback_sum = dry + feedback_attenuated; - - dsp_write_delay( &sch->M, &input_feedback_sum ); - - *output = delay_output - input_feedback_sum*sch->gain; -} - -/* temporary global design */ -static struct dsp_lpf __lpf_mud_free; -static struct dsp_delay __echos[8]; - -#ifdef VG_ECHO_LPF_BUTTERWORTH -static struct dsp_biquad __echos_lpf[8]; -#else -static struct dsp_lpf __echos_lpf[8]; -#endif -static struct dsp_schroeder __diffusion_chain[8]; - -static void async_vg_dsp_alloc_texture( void *payload, u32 size ) -{ - glGenTextures( 1, &vg_dsp.view_texture ); - glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, - GL_RGBA, GL_UNSIGNED_BYTE, vg_dsp.view_texture_buffer ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); -} - -static void vg_dsp_init( void ){ - vg_rand_seed( &vg_dsp.rand, 461 ); - vg_dsp.buffer = vg_linear_alloc( vg_mem.rtmemory, 1024*1024*1 ); - vg_dsp.view_texture_buffer = vg_linear_alloc( vg_mem.rtmemory, 512*512 ); - - vg_async_call( async_vg_dsp_alloc_texture, NULL, 0 ); - - /* temporary global design */ - dsp_init_lpf( &__lpf_mud_free, 125.0f ); - - float sizes[] = - { 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f }; - - float variance = 0.1f; - - for( int i=0; i<8; i++ ){ - float reflection_time = ((sizes[i])/343.0f) * 1000.0f; - - float var = 1.0f + (vg_randf64(&vg_dsp.rand)*2.0f - 1.0f) * variance, - total = reflection_time * var; - - dsp_init_delay( &__echos[i], total / 1000.0f ); - - float freq = vg_lerpf( 800.0f, 350.0f, sizes[i] / 256.0f ); - -#ifdef VG_ECHO_LPF_BUTTERWORTH - dsp_init_biquad_butterworth_lpf( &__echos_lpf[i], freq ); -#else - dsp_init_lpf( &__echos_lpf[i], freq ); -#endif - } - - float diffusions[] = { 187.0f, 159.0f, 143.0f, 121.0f, - 79.0f, 57.0f, 27.0f, 11.0f }; - - for( int i=0; i<8; i++ ){ - dsp_init_schroeder( __diffusion_chain+i, diffusions[i]/1000.0f, 0.7f ); - } -} - -static void vg_dsp_process( float *stereo_in, float *stereo_out ) -{ - float in_total = (stereo_in[0]+stereo_in[1])*0.5f; - float recieved = 0.0f; - - for( int i=0; i<8; i++ ){ - f32 echo; - dsp_read_delay( __echos+i, &echo, 1 ); - -#ifdef VG_ECHO_LPF_BUTTERWORTH - echo = dsp_biquad_process( __echos_lpf+i, echo ); -#else - dsp_write_lpf( __echos_lpf+i, &echo ); - dsp_read_lpf( __echos_lpf+i, &echo ); -#endif - - recieved += echo * vg_dsp.echo_tunings[i]*0.98; - } - - float diffused = recieved; - - for( int i=0; i<8; i++ ){ - dsp_process_schroeder( __diffusion_chain+i, &diffused, &diffused ); - } - - float diffuse_mix = vg_dsp.reverb_wet_mix; - diffuse_mix = vg_lerpf( recieved, diffused, diffuse_mix ); - float total = in_total + diffuse_mix; - - float low_mud; - dsp_write_lpf( &__lpf_mud_free, &total ); - dsp_read_lpf( &__lpf_mud_free, &low_mud ); - - total -= low_mud; - - for( int i=0; i<8; i++ ) - dsp_write_delay( __echos+i, &total ); - - stereo_out[0] = stereo_in[0]*vg_dsp.reverb_dry_mix; - stereo_out[1] = stereo_in[1]*vg_dsp.reverb_dry_mix; - stereo_out[0] += diffuse_mix*2.0f*vg_dsp.reverb_wet_mix; - stereo_out[1] += diffuse_mix*2.0f*vg_dsp.reverb_wet_mix; -} - -static void dsp_update_tunings(void) -{ - float sizes[] = - { 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f }; - float volumes[] = - { 0.2f, 0.3f, 0.5f, 0.7f, 0.8f, 0.9f, 1.0f, 1.0f }; - - float avg_distance = 0.0f; - - for( int i=0; i<8; i++ ) - vg_dsp.echo_tunings[i] = 0.5f; - - for( int j=0; j<14; j++ ){ - float d = vg_dsp.echo_distances[j]; - - for( int i=0; i<7; i++ ){ - if( d < sizes[i+1] ){ - float range = sizes[i+1]-sizes[i]; - float t = vg_clampf( (d - sizes[i])/range, 0.0f, 1.0f ); - - vg_dsp.echo_tunings[i ] += 1.0f-t; - vg_dsp.echo_tunings[i+1] += t; - - break; - } - } - - avg_distance += d; - } - avg_distance /= 14.0f; - - - vg_dsp.reverb_wet_mix =1.0f-vg_clampf((avg_distance-30.0f)/200.0f,0.0f,1.0f); - vg_dsp.reverb_dry_mix =1.0f-vg_dsp.reverb_wet_mix*0.4f; - - float total = 0.0f; - for( int i=0; i<8; i++ ) - total += vg_dsp.echo_tunings[i]; - - if( total > 0.0f ){ - float inverse = 1.0f/total; - - for( int i=0;i<8; i++ ){ - vg_dsp.echo_tunings[i] *= inverse; - } - } - - for( int i=0; i<8; i++ ){ - float freq = vg_lerpf( 200.0f, 500.0f, vg_dsp.echo_tunings[i] ); - -#ifdef VG_ECHO_LPF_BUTTERWORTH - dsp_init_biquad_butterworth_lpf( &__echos_lpf[i], freq ); -#else - dsp_update_lpf( &__echos_lpf[i], freq ); -#endif - } - - for( int i=0;i<8; i++ ){ - vg_dsp.echo_tunings[i] *= volumes[i]; - } -} - -static void vg_dsp_free( void ) -{ - glDeleteTextures( 1, &vg_dsp.view_texture ); -} - -static void vg_dsp_update_texture( void ) -{ - for( int i=0; i<512*512; i++ ){ - float v = vg_clampf( vg_dsp.buffer[i] * 0.5f + 0.5f, 0.0f, 1.0f ); - vg_dsp.view_texture_buffer[i] = v * 255.0f; - } -} - -#endif /* VG_AUDIO_DSP_H */ +void vg_dsp_init( void ); +void vg_dsp_free( void ); +void dsp_update_tunings(void); +void vg_dsp_process( float *stereo_in, float *stereo_out ); +void vg_dsp_update_texture( void ); + +f32 dsp_biquad_process( struct dsp_biquad *bq, f32 xn ); +void dsp_init_biquad_butterworth_lpf( struct dsp_biquad *bq, f32 fc ); +void dsp_read_delay( struct dsp_delay *delay, float *s, u32 t ); +void dsp_write_delay( struct dsp_delay *delay, float *s ); +void dsp_init_delay( struct dsp_delay *delay, float length ); +void dsp_update_lpf( struct dsp_lpf *lpf, float freq ); +void dsp_init_lpf( struct dsp_lpf *lpf, float freq ); +void dsp_write_lpf( struct dsp_lpf *lpf, float *s ); +void dsp_read_lpf( struct dsp_lpf *lpf, float *s ); +void dsp_init_schroeder( struct dsp_schroeder *sch, float length, float gain ); +void dsp_process_schroeder( struct dsp_schroeder *sch, + float *input, float *output ); diff --git a/vg_audio_synth_bird.c b/vg_audio_synth_bird.c new file mode 100644 index 0000000..eecad3b --- /dev/null +++ b/vg_audio_synth_bird.c @@ -0,0 +1,419 @@ +#include "vg_audio_synth_bird.h" +#include "vg_binstr.h" + +#define DEFAULT_VOL 1.0f,0.5f,0.2f,0.125f +#define DEFAULT_TONES { {1,1}, {6,5}, {8,7}, {13,12} } +#define DEFAULT_RISE 0.00090702947f +#define DEFAULT_FALL 0.00226757369f + +static struct synth_bird_settings synth_bird__default_settings = +{ + .tones = DEFAULT_TONES, + .type = k_bird_lfo_sine_approx, + .adsr_rise = DEFAULT_RISE, + .adsr_fall = DEFAULT_FALL +}; + +static struct synth_bird_signature synth_bird__warbling_vireo[] = +{ + /* timing fundemental volumes lfo hz,depth */ + {0.13,0.10, 4000,100,100,0, DEFAULT_VOL, 60,200 }, + {0.10,0.05, 4200,-500,1700,0, DEFAULT_VOL, 60,96 }, + {0.10,0.00, 2400,-1200,1000,1700, DEFAULT_VOL, 60,96 }, + {0.06,0.04, 3100,200,-10,-1100, DEFAULT_VOL, 60,90 }, + {0.13,0.07, 4600,-2000,0,1300, DEFAULT_VOL, 60,10 }, + {0.05,0.00, 2700,-300,700,800, DEFAULT_VOL, 60,10 }, + {0.09,0.07, 3600,-300,0,0, DEFAULT_VOL, 60,20 }, + {0.05,0.07, 4800,1240,300,0, DEFAULT_VOL, 60,20 }, + {0.08,0.02, 2700,-800,150,1000, DEFAULT_VOL, 60,160 }, + {0.12,0.08, 2700,-800,150,1000, DEFAULT_VOL, 60,160 }, + {0.10,0.04, 6300,-100,-3200,1000, DEFAULT_VOL, 60,100 }, + {0.16,0.10, 4260,-200,300,1100, DEFAULT_VOL, 60,20 } +}; + +static struct synth_bird_signature synth_bird__pied_monarch[] = +{ + /* timing fundemental volumes lfo hz,depth */ + {0.18,0.13, 2200,700,-300,0, 0.6,0.05,0,0, 60,0 }, + {0.17,0.12, 2200,700,-300,0, 0.8,0.05,0,0, 60,0 }, + {0.16,0.11, 2200,700,-300,0, 0.9,0.05,0,0, 60,0 }, + {0.14,0.09, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.12,0.07, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.11,0.06, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, + {0.10,0.10, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 } +}; + +static struct synth_bird_signature synth_bird__bridled_honeyeater[] = +{ + /* timing fundemental volumes lfo hz,depth */ + {0.10,0.10, 2000,-1000,600,0, 1.00,0.00,0.00,0.00, 30,60}, + {0.10,0.10, 4000,0,-200,-200, 0.80,0.25,0.25,0.25, 30,60}, + {0.06,0.01, 4000,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, + {0.07,0.01, 3950,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, + {0.08,0.01, 3900,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, + {0.09,0.01, 3850,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, + {0.10,0.02, 3800,0,-700,-800, 0.90,0.20,0.10,0.00, 60,20}, + {0.11,0.05, 3750,0,-700,-800, 0.90,0.40,0.20,0.00, 60,20}, + {0.12,0.20, 3700,0,-700,-800, 0.30,0.10,0.00,0.00, 60,20}, + {0.10,0.10, 2600,1300,600,0, 0.97,0.03,0.00,0.00, 60,20}, +}; + +static struct synth_bird_signature synth_bird__cricket[] = +{ + /* timing fundemental volumes lfo hz, depth */ + {0.10,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.11,0.14, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.13,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.09,0.16, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.10,0.12, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.10,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.11,0.14, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.13,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.09,0.16, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, + {0.10,0.12, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200} +}; + +static struct synth_bird_signature synth_bird__gray_shrikethrush[] = +{ + /* timing fundemental volumes lfo hz, depth */ + { 0.13,0.1, 2600,-200,-100,200, 0.9,0.1,0.05,0.001, 60,10 } +}; + +static struct synth_bird_signature synth_bird__boobook[] = +{ + /* timing fundemental volumes lfo hz, depth */ + {0.3,0.14, 700,0,-100,100, 0.9,0.14,0.0,0.2, 30,18}, + {0.3,1.20, 630,0,-100,100, 0.9,0.00,0.3,0.0, 30,18} +}; + +static struct synth_bird_signature synth_bird__shrike_tit[] = +{ + /* timing fundemental volumes lfo hz, depth */ + {0.6,1.4, 2300,-300,-100,100, 1.0,0.14,0.0,0.1, 60,5 } +}; + +/* sine functions over the range [0, 44100] : [-pi, pi]. + * Not accurate! */ + +static float sine_1second_1( int o ) +{ + float s = (o<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; + float t = ((float)o*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s*0.5f; + float t2 = t*t; + float t4 = t2*t2; + return s*(5.0f*t2-4.0f*t4-1.0f); +} + +static void sine_1second_4( int o[4], float v[4] ) +{ + float s[4],t[4],t2[4],t4[4]; + s[0] = (o[0]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; + s[1] = (o[1]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; + s[2] = (o[2]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; + s[3] = (o[3]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; + + t[0] = ((float)o[0]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[0]*0.5f; + t[1] = ((float)o[1]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[1]*0.5f; + t[2] = ((float)o[2]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[2]*0.5f; + t[3] = ((float)o[3]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[3]*0.5f; + + t2[0] = t[0]*t[0]; + t2[1] = t[1]*t[1]; + t2[2] = t[2]*t[2]; + t2[3] = t[3]*t[3]; + + t4[0] = t2[0]*t2[0]; + t4[1] = t2[1]*t2[1]; + t4[2] = t2[2]*t2[2]; + t4[3] = t2[3]*t2[3]; + + v[0] = s[0]*(5.0f*t2[0]-4.0f*t4[0]-1.0f); + v[1] = s[1]*(5.0f*t2[1]-4.0f*t4[1]-1.0f); + v[2] = s[2]*(5.0f*t2[2]-4.0f*t4[2]-1.0f); + v[3] = s[3]*(5.0f*t2[3]-4.0f*t4[3]-1.0f); +} + +static float saw_1second_1( int o ) +{ + float t = ((float)o*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f, + tt = t*t, + ttt = tt*t; + + return -2.5f*ttt+2.5f*t; +} + +u32 synth_bird_get_length_in_samples( struct synth_bird *bird ) +{ + u32 total = 0; + + for( int i=0; isettings.pattern_length; i ++ ){ + struct synth_bird_signature *sig = &bird->settings.pattern[i]; + + u32 l = sig->length * (float)BIRD_SAMPLE_RATE, + p = sig->pause * (float)BIRD_SAMPLE_RATE; + + total += l+p; + } + + return total; +} + +void synth_bird_reset( struct synth_bird *bird ) +{ + bird->rt.osc_main[0] = 0; + bird->rt.osc_main[1] = 0; + bird->rt.osc_main[2] = 0; + bird->rt.osc_main[3] = 0; + bird->rt.osc_lfo = 0; + + bird->rt.volume[0] = 0.0f; + bird->rt.volume[1] = 0.0f; + bird->rt.volume[2] = 0.0f; + bird->rt.volume[3] = 0.0f; + + bird->rt.fundamental = 0.0f; + bird->rt.x = 0; + bird->rt.length = bird->settings.pattern[0].length * (float)BIRD_SAMPLE_RATE; + bird->rt.gate = 1; + bird->rt.adsr = 0; + bird->rt.frame = 0; + bird->rt.lfo_hz = 0; + bird->rt.fm_depth = 0.0f; + + bird->rt.adsr_rise = bird->settings.adsr_rise * (float)BIRD_SAMPLE_RATE; + bird->rt.adsr_fall = bird->settings.adsr_fall * (float)BIRD_SAMPLE_RATE; +} + +static u32 synth_bird_save_size( struct synth_bird *bird ) +{ + return sizeof(struct synth_bird_signature) * bird->settings.pattern_length + + sizeof(struct synth_bird_settings); +} + +static void synth_bird_save( struct synth_bird *bird, void *txt ) +{ + void *src = &bird->settings; + vg_bin_str( src, txt, synth_bird_save_size( bird ) ); +} + +#if 0 +static void synth_bird_load( struct synth_bird *bird, + const char *txt, u32 length ) +{ + vg_str_bin( txt, &bird->settings, length ); + synth_bird_reset( bird ); +} + +/* expects a null terminated string */ +static u32 synth_bird_memory_requirement( u32 string_length ) +{ + return (string_length/2) + + sizeof(struct synth_bird) - sizeof(struct synth_bird_settings); +} +#endif + +#ifdef SYNTH_BIRD_STDLIB +#include "stdlib.h" +#include "string.h" + +static struct synth_bird *synth_bird_create( + struct synth_bird_settings *settings, + struct synth_bird_signature *pattern, + u32 pattern_length ) +{ + u32 pattern_size = sizeof( struct synth_bird_signature ) * pattern_length; + u32 total_size = sizeof( struct synth_bird ) + pattern_size; + struct synth_bird *bird = malloc( total_size ); + + bird->settings = *settings; + + memcpy( bird->settings.pattern, pattern, pattern_size ); + bird->settings.pattern_length = pattern_length; + + synth_bird_reset( bird ); + return bird; +} + +#endif + +static void synth_bird_think( struct synth_bird *bird ) +{ + struct synth_bird_signature *sig = &bird->settings.pattern[ bird->rt.frame ]; + + bird->rt.x ++; + if( bird->rt.x >= bird->rt.length ) + { + if( bird->rt.gate && (sig->pause != 0.0f) ) + { + bird->rt.gate = 0; + bird->rt.length = sig->pause * (float)BIRD_SAMPLE_RATE; + } + else + { + bird->rt.frame ++; + + if( bird->rt.frame >= bird->settings.pattern_length ) + bird->rt.frame = 0; + + sig = &bird->settings.pattern[ bird->rt.frame ]; + + bird->rt.gate = 1; + bird->rt.length = sig->length * (float)BIRD_SAMPLE_RATE; + } + + bird->rt.x = 0; + } + + if( bird->rt.gate ) + { + bird->rt.adsr += bird->rt.adsr_rise; + if( bird->rt.adsr > BIRD_SAMPLE_RATE ) + bird->rt.adsr = BIRD_SAMPLE_RATE; + } + else + { + bird->rt.adsr -= bird->rt.adsr_fall; + if( bird->rt.adsr < 0 ) + bird->rt.adsr = 0; + } + + if( bird->rt.gate ) + { + float l = (float)bird->rt.length, + t = ((float)bird->rt.x * (1.0f/l))*2.0f - 1.0f, + tt = t*t, + ttt = tt*t; + + bird->rt.fundamental = sig->x0 + t*sig->x1 + tt*sig->x2 + ttt*sig->x3; + } + + float vol = (float)bird->rt.adsr * (1.0f/(float)BIRD_SAMPLE_RATE); + + bird->rt.fm_depth = sig->fm; + bird->rt.lfo_hz = sig->lfo_hz; + bird->rt.volume[0] = sig->v0 * vol; + bird->rt.volume[1] = sig->v1 * vol; + bird->rt.volume[2] = sig->v2 * vol; + bird->rt.volume[3] = sig->v3 * vol; +} + +static inline void int_add_mod( int *a, int const b ) +{ + *a += b; + + if( *a > BIRD_SAMPLE_RATE ) + *a -= BIRD_SAMPLE_RATE; +} + +void synth_bird_generate_samples( struct synth_bird *bird, + float *stereo_buffer, int samples ) +{ + for( int _=0; _rt.osc_lfo, bird->rt.lfo_hz ); + float fm = sine_1second_1( bird->rt.osc_lfo ) * bird->rt.fm_depth; + + int freq = bird->rt.fundamental + fm; + int hz[4] = + { + (freq * bird->settings.tones[0][0]) / bird->settings.tones[0][1], + (freq * bird->settings.tones[1][0]) / bird->settings.tones[1][1], + (freq * bird->settings.tones[2][0]) / bird->settings.tones[2][1], + (freq * bird->settings.tones[3][0]) / bird->settings.tones[3][1], + }; + + int_add_mod( bird->rt.osc_main + 0, hz[0] ); + int_add_mod( bird->rt.osc_main + 1, hz[1] ); + int_add_mod( bird->rt.osc_main + 2, hz[2] ); + int_add_mod( bird->rt.osc_main + 3, hz[3] ); + + float v[4]; + sine_1second_4( bird->rt.osc_main, v ); + + float s = v[0] * bird->rt.volume[0] + + v[1] * bird->rt.volume[1] + + v[2] * bird->rt.volume[2] + + v[3] * bird->rt.volume[3] ; + + stereo_buffer[ _*2+0 ] = s; + stereo_buffer[ _*2+1 ] = s; + } +} + +#ifdef SYNTH_BIRD_STDLIB +#include "stdio.h" + +#define KNRM "\x1B[00m" +#define KRED "\x1B[31m" +#define KGRN "\x1B[32m" +#define KYEL "\x1B[33m" +#define KBLU "\x1B[34m" +#define KMAG "\x1B[35m" +#define KCYN "\x1B[36m" +#define KWHT "\x1B[37m" + +#define LOG_BAR0 " . |" +#define LOG_BAR1 "-------+-------+-------+-------+" +#define LOG_BAR2 " " + +static void synth_bird_log_pattern( struct synth_bird *bird ) +{ + synth_bird_reset( bird ); + + char output[][400]= { +KNRM "9k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KNRM " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KNRM "8k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KNRM " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KNRM "7k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KNRM " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KMAG "6k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KMAG " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KCYN "5k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KCYN " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KBLU "4k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KBLU " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KGRN "3k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KGRN " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KYEL "2k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KYEL " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KRED "1k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KRED " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, +KWHT " +" LOG_BAR1 LOG_BAR1 LOG_BAR1 LOG_BAR1 LOG_BAR1 +KWHT " 0.0s"LOG_BAR2"0.5s"LOG_BAR2"1.0s"LOG_BAR2"1.5s"LOG_BAR2"2.0s"LOG_BAR2 + }; + + for( int i=0; i<190*8; i++ ) + { + for( int j=0; jrt.fundamental / 500.0f; + int j = hz; + + if( j < 0 ) j = 0; + if( j > 18 ) j = 18; + j = 18-j; + + float level = bird->rt.adsr; + level *= (1.0f/(float)BIRD_SAMPLE_RATE); + + int ch = level*3.0f; + + if( ch ) + output[j][(i/8)+7] = " *###"[ch]; + } + + for( int i=0; i<20; i++ ) + { + puts( output[i] ); + } +} +#endif diff --git a/vg_audio_synth_bird.h b/vg_audio_synth_bird.h index ff6ef50..0a2f418 100644 --- a/vg_audio_synth_bird.h +++ b/vg_audio_synth_bird.h @@ -1,14 +1,11 @@ -#ifndef VG_AUDIO_SYNTH_BIRD_H -#define VG_AUDIO_SYNTH_BIRD_H - -#include "vg_binstr.h" -#include "vg_stdint.h" +#pragma once #ifndef BIRD_SAMPLE_RATE #define BIRD_SAMPLE_RATE 44100 #endif -struct synth_bird_signature{ +struct synth_bird_signature +{ float length, pause, /* timings in seconds */ x0,x1,x2,x3, /* polynomial coefficients for the fundemental */ v0,v1,v2,v3; /* volume of each oscillator */ @@ -17,7 +14,8 @@ struct synth_bird_signature{ float fm; /* LFO modulation depth (+/- hz) */ }; -struct synth_bird{ +struct synth_bird +{ struct{ int osc_main[4], osc_lfo; @@ -54,421 +52,7 @@ struct synth_bird{ settings; }; -#define DEFAULT_VOL 1.0f,0.5f,0.2f,0.125f -#define DEFAULT_TONES { {1,1}, {6,5}, {8,7}, {13,12} } -#define DEFAULT_RISE 0.00090702947f -#define DEFAULT_FALL 0.00226757369f - -static struct synth_bird_settings synth_bird__default_settings = -{ - .tones = DEFAULT_TONES, - .type = k_bird_lfo_sine_approx, - .adsr_rise = DEFAULT_RISE, - .adsr_fall = DEFAULT_FALL -}; - -static struct synth_bird_signature synth_bird__warbling_vireo[] = -{ - /* timing fundemental volumes lfo hz,depth */ - {0.13,0.10, 4000,100,100,0, DEFAULT_VOL, 60,200 }, - {0.10,0.05, 4200,-500,1700,0, DEFAULT_VOL, 60,96 }, - {0.10,0.00, 2400,-1200,1000,1700, DEFAULT_VOL, 60,96 }, - {0.06,0.04, 3100,200,-10,-1100, DEFAULT_VOL, 60,90 }, - {0.13,0.07, 4600,-2000,0,1300, DEFAULT_VOL, 60,10 }, - {0.05,0.00, 2700,-300,700,800, DEFAULT_VOL, 60,10 }, - {0.09,0.07, 3600,-300,0,0, DEFAULT_VOL, 60,20 }, - {0.05,0.07, 4800,1240,300,0, DEFAULT_VOL, 60,20 }, - {0.08,0.02, 2700,-800,150,1000, DEFAULT_VOL, 60,160 }, - {0.12,0.08, 2700,-800,150,1000, DEFAULT_VOL, 60,160 }, - {0.10,0.04, 6300,-100,-3200,1000, DEFAULT_VOL, 60,100 }, - {0.16,0.10, 4260,-200,300,1100, DEFAULT_VOL, 60,20 } -}; - -static struct synth_bird_signature synth_bird__pied_monarch[] = -{ - /* timing fundemental volumes lfo hz,depth */ - {0.18,0.13, 2200,700,-300,0, 0.6,0.05,0,0, 60,0 }, - {0.17,0.12, 2200,700,-300,0, 0.8,0.05,0,0, 60,0 }, - {0.16,0.11, 2200,700,-300,0, 0.9,0.05,0,0, 60,0 }, - {0.14,0.09, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.12,0.07, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.11,0.06, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.10,0.05, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 }, - {0.10,0.10, 2200,700,-300,0, 1.0,0.05,0,0, 60,0 } -}; - -static struct synth_bird_signature synth_bird__bridled_honeyeater[] = -{ - /* timing fundemental volumes lfo hz,depth */ - {0.10,0.10, 2000,-1000,600,0, 1.00,0.00,0.00,0.00, 30,60}, - {0.10,0.10, 4000,0,-200,-200, 0.80,0.25,0.25,0.25, 30,60}, - {0.06,0.01, 4000,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, - {0.07,0.01, 3950,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, - {0.08,0.01, 3900,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, - {0.09,0.01, 3850,0,-700,-800, 0.90,0.10,0.00,0.00, 60,20}, - {0.10,0.02, 3800,0,-700,-800, 0.90,0.20,0.10,0.00, 60,20}, - {0.11,0.05, 3750,0,-700,-800, 0.90,0.40,0.20,0.00, 60,20}, - {0.12,0.20, 3700,0,-700,-800, 0.30,0.10,0.00,0.00, 60,20}, - {0.10,0.10, 2600,1300,600,0, 0.97,0.03,0.00,0.00, 60,20}, -}; - -static struct synth_bird_signature synth_bird__cricket[] = -{ - /* timing fundemental volumes lfo hz, depth */ - {0.10,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.11,0.14, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.13,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.09,0.16, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.10,0.12, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.10,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.11,0.14, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.13,0.15, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.09,0.16, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200}, - {0.10,0.12, 5000,0,0,100, 0.25,0.25,0.25,0.25, 40,200} -}; - -static struct synth_bird_signature synth_bird__gray_shrikethrush[] = -{ - /* timing fundemental volumes lfo hz, depth */ - { 0.13,0.1, 2600,-200,-100,200, 0.9,0.1,0.05,0.001, 60,10 } -}; - -static struct synth_bird_signature synth_bird__boobook[] = -{ - /* timing fundemental volumes lfo hz, depth */ - {0.3,0.14, 700,0,-100,100, 0.9,0.14,0.0,0.2, 30,18}, - {0.3,1.20, 630,0,-100,100, 0.9,0.00,0.3,0.0, 30,18} -}; - -static struct synth_bird_signature synth_bird__shrike_tit[] = -{ - /* timing fundemental volumes lfo hz, depth */ - {0.6,1.4, 2300,-300,-100,100, 1.0,0.14,0.0,0.1, 60,5 } -}; - -/* sine functions over the range [0, 44100] : [-pi, pi]. - * Not accurate! */ - -static float sine_1second_1( int o ) -{ - float s = (o<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; - float t = ((float)o*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s*0.5f; - float t2 = t*t; - float t4 = t2*t2; - return s*(5.0f*t2-4.0f*t4-1.0f); -} - -static void sine_1second_4( int o[4], float v[4] ) -{ - float s[4],t[4],t2[4],t4[4]; - s[0] = (o[0]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; - s[1] = (o[1]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; - s[2] = (o[2]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; - s[3] = (o[3]<(BIRD_SAMPLE_RATE/2))?-1.0f:1.0f; - - t[0] = ((float)o[0]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[0]*0.5f; - t[1] = ((float)o[1]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[1]*0.5f; - t[2] = ((float)o[2]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[2]*0.5f; - t[3] = ((float)o[3]*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f - s[3]*0.5f; - - t2[0] = t[0]*t[0]; - t2[1] = t[1]*t[1]; - t2[2] = t[2]*t[2]; - t2[3] = t[3]*t[3]; - - t4[0] = t2[0]*t2[0]; - t4[1] = t2[1]*t2[1]; - t4[2] = t2[2]*t2[2]; - t4[3] = t2[3]*t2[3]; - - v[0] = s[0]*(5.0f*t2[0]-4.0f*t4[0]-1.0f); - v[1] = s[1]*(5.0f*t2[1]-4.0f*t4[1]-1.0f); - v[2] = s[2]*(5.0f*t2[2]-4.0f*t4[2]-1.0f); - v[3] = s[3]*(5.0f*t2[3]-4.0f*t4[3]-1.0f); -} - -static float saw_1second_1( int o ) -{ - float t = ((float)o*(1.0f/(float)(BIRD_SAMPLE_RATE/2)))-1.0f, - tt = t*t, - ttt = tt*t; - - return -2.5f*ttt+2.5f*t; -} - -static u32 synth_bird_get_length_in_samples( struct synth_bird *bird ) -{ - u32 total = 0; - - for( int i=0; isettings.pattern_length; i ++ ){ - struct synth_bird_signature *sig = &bird->settings.pattern[i]; - - u32 l = sig->length * (float)BIRD_SAMPLE_RATE, - p = sig->pause * (float)BIRD_SAMPLE_RATE; - - total += l+p; - } - - return total; -} - -static void synth_bird_reset( struct synth_bird *bird ) -{ - bird->rt.osc_main[0] = 0; - bird->rt.osc_main[1] = 0; - bird->rt.osc_main[2] = 0; - bird->rt.osc_main[3] = 0; - bird->rt.osc_lfo = 0; - - bird->rt.volume[0] = 0.0f; - bird->rt.volume[1] = 0.0f; - bird->rt.volume[2] = 0.0f; - bird->rt.volume[3] = 0.0f; - - bird->rt.fundamental = 0.0f; - bird->rt.x = 0; - bird->rt.length = bird->settings.pattern[0].length * (float)BIRD_SAMPLE_RATE; - bird->rt.gate = 1; - bird->rt.adsr = 0; - bird->rt.frame = 0; - bird->rt.lfo_hz = 0; - bird->rt.fm_depth = 0.0f; - - bird->rt.adsr_rise = bird->settings.adsr_rise * (float)BIRD_SAMPLE_RATE; - bird->rt.adsr_fall = bird->settings.adsr_fall * (float)BIRD_SAMPLE_RATE; -} - -static u32 synth_bird_save_size( struct synth_bird *bird ) -{ - return sizeof(struct synth_bird_signature) * bird->settings.pattern_length + - sizeof(struct synth_bird_settings); -} - -static void synth_bird_save( struct synth_bird *bird, void *txt ) -{ - void *src = &bird->settings; - vg_bin_str( src, txt, synth_bird_save_size( bird ) ); -} - -#if 0 -static void synth_bird_load( struct synth_bird *bird, - const char *txt, u32 length ) -{ - vg_str_bin( txt, &bird->settings, length ); - synth_bird_reset( bird ); -} - -/* expects a null terminated string */ -static u32 synth_bird_memory_requirement( u32 string_length ) -{ - return (string_length/2) + - sizeof(struct synth_bird) - sizeof(struct synth_bird_settings); -} -#endif - -#ifdef SYNTH_BIRD_STDLIB -#include "stdlib.h" -#include "string.h" - -static struct synth_bird *synth_bird_create( - struct synth_bird_settings *settings, - struct synth_bird_signature *pattern, - u32 pattern_length ) -{ - u32 pattern_size = sizeof( struct synth_bird_signature ) * pattern_length; - u32 total_size = sizeof( struct synth_bird ) + pattern_size; - struct synth_bird *bird = malloc( total_size ); - - bird->settings = *settings; - - memcpy( bird->settings.pattern, pattern, pattern_size ); - bird->settings.pattern_length = pattern_length; - - synth_bird_reset( bird ); - return bird; -} - -#endif - -static void synth_bird_think( struct synth_bird *bird ) -{ - struct synth_bird_signature *sig = &bird->settings.pattern[ bird->rt.frame ]; - - bird->rt.x ++; - if( bird->rt.x >= bird->rt.length ) - { - if( bird->rt.gate && (sig->pause != 0.0f) ) - { - bird->rt.gate = 0; - bird->rt.length = sig->pause * (float)BIRD_SAMPLE_RATE; - } - else - { - bird->rt.frame ++; - - if( bird->rt.frame >= bird->settings.pattern_length ) - bird->rt.frame = 0; - - sig = &bird->settings.pattern[ bird->rt.frame ]; - - bird->rt.gate = 1; - bird->rt.length = sig->length * (float)BIRD_SAMPLE_RATE; - } - - bird->rt.x = 0; - } - - if( bird->rt.gate ) - { - bird->rt.adsr += bird->rt.adsr_rise; - if( bird->rt.adsr > BIRD_SAMPLE_RATE ) - bird->rt.adsr = BIRD_SAMPLE_RATE; - } - else - { - bird->rt.adsr -= bird->rt.adsr_fall; - if( bird->rt.adsr < 0 ) - bird->rt.adsr = 0; - } - - if( bird->rt.gate ) - { - float l = (float)bird->rt.length, - t = ((float)bird->rt.x * (1.0f/l))*2.0f - 1.0f, - tt = t*t, - ttt = tt*t; - - bird->rt.fundamental = sig->x0 + t*sig->x1 + tt*sig->x2 + ttt*sig->x3; - } - - float vol = (float)bird->rt.adsr * (1.0f/(float)BIRD_SAMPLE_RATE); - - bird->rt.fm_depth = sig->fm; - bird->rt.lfo_hz = sig->lfo_hz; - bird->rt.volume[0] = sig->v0 * vol; - bird->rt.volume[1] = sig->v1 * vol; - bird->rt.volume[2] = sig->v2 * vol; - bird->rt.volume[3] = sig->v3 * vol; -} - -static inline void int_add_mod( int *a, int const b ) -{ - *a += b; - - if( *a > BIRD_SAMPLE_RATE ) - *a -= BIRD_SAMPLE_RATE; -} - -static void synth_bird_generate_samples( struct synth_bird *bird, - float *stereo_buffer, int samples ) -{ - for( int _=0; _rt.osc_lfo, bird->rt.lfo_hz ); - float fm = sine_1second_1( bird->rt.osc_lfo ) * bird->rt.fm_depth; - - int freq = bird->rt.fundamental + fm; - int hz[4] = - { - (freq * bird->settings.tones[0][0]) / bird->settings.tones[0][1], - (freq * bird->settings.tones[1][0]) / bird->settings.tones[1][1], - (freq * bird->settings.tones[2][0]) / bird->settings.tones[2][1], - (freq * bird->settings.tones[3][0]) / bird->settings.tones[3][1], - }; - - int_add_mod( bird->rt.osc_main + 0, hz[0] ); - int_add_mod( bird->rt.osc_main + 1, hz[1] ); - int_add_mod( bird->rt.osc_main + 2, hz[2] ); - int_add_mod( bird->rt.osc_main + 3, hz[3] ); - - float v[4]; - sine_1second_4( bird->rt.osc_main, v ); - - float s = v[0] * bird->rt.volume[0] + - v[1] * bird->rt.volume[1] + - v[2] * bird->rt.volume[2] + - v[3] * bird->rt.volume[3] ; - - stereo_buffer[ _*2+0 ] = s; - stereo_buffer[ _*2+1 ] = s; - } -} - -#ifdef SYNTH_BIRD_STDLIB -#include "stdio.h" - -#define KNRM "\x1B[00m" -#define KRED "\x1B[31m" -#define KGRN "\x1B[32m" -#define KYEL "\x1B[33m" -#define KBLU "\x1B[34m" -#define KMAG "\x1B[35m" -#define KCYN "\x1B[36m" -#define KWHT "\x1B[37m" - -#define LOG_BAR0 " . |" -#define LOG_BAR1 "-------+-------+-------+-------+" -#define LOG_BAR2 " " - -static void synth_bird_log_pattern( struct synth_bird *bird ) -{ - synth_bird_reset( bird ); - - char output[][400]= { -KNRM "9k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KNRM " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KNRM "8k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KNRM " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KNRM "7k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KNRM " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KMAG "6k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KMAG " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KCYN "5k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KCYN " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KBLU "4k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KBLU " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KGRN "3k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KGRN " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KYEL "2k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KYEL " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KRED "1k-" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KRED " -" LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0 LOG_BAR0, -KWHT " +" LOG_BAR1 LOG_BAR1 LOG_BAR1 LOG_BAR1 LOG_BAR1 -KWHT " 0.0s"LOG_BAR2"0.5s"LOG_BAR2"1.0s"LOG_BAR2"1.5s"LOG_BAR2"2.0s"LOG_BAR2 - }; - - for( int i=0; i<190*8; i++ ) - { - for( int j=0; jrt.fundamental / 500.0f; - int j = hz; - - if( j < 0 ) j = 0; - if( j > 18 ) j = 18; - j = 18-j; - - float level = bird->rt.adsr; - level *= (1.0f/(float)BIRD_SAMPLE_RATE); - - int ch = level*3.0f; - - if( ch ) - output[j][(i/8)+7] = " *###"[ch]; - } - - for( int i=0; i<20; i++ ) - { - puts( output[i] ); - } -} -#endif - -#endif /* VG_AUDIO_SYNTH_BIRD_H */ +void synth_bird_reset( struct synth_bird *bird ); +u32 synth_bird_get_length_in_samples( struct synth_bird *bird ); +void synth_bird_generate_samples( struct synth_bird *bird, + float *stereo_buffer, int samples ); diff --git a/vg_binstr.c b/vg_binstr.c new file mode 100644 index 0000000..7d9ac83 --- /dev/null +++ b/vg_binstr.c @@ -0,0 +1,23 @@ +#include "vg/vg_binstr.h" + +void vg_str_bin( const void *txt, void *bin, int size ) +{ + const u8 *src = txt; + u8 *dst = bin; + + for( u32 i=0; i>4u) & 0xf); + } +} diff --git a/vg_binstr.h b/vg_binstr.h index d6bfb83..6e662fe 100644 --- a/vg_binstr.h +++ b/vg_binstr.h @@ -1,32 +1,10 @@ -#ifndef VG_BINSTR -#define VG_BINSTR +#pragma once /* dead simple.. 4 bits/character encoding */ -#include "vg_stdint.h" +#include "vg_platform.h" #define VG_BINSTR_BASECHAR 0x41 -static void vg_str_bin( const void *txt, void *bin, int size ) -{ - const u8 *src = txt; - u8 *dst = bin; - - for( u32 i=0; i>4u) & 0xf); - } -} - -#endif /* VG_BINSTR */ +void vg_str_bin( const void *txt, void *bin, int size ); +void vg_bin_str( const void *bin, void *txt, u32 size ); diff --git a/vg_build.h b/vg_build.h index a30fe17..9e5d9a7 100644 --- a/vg_build.h +++ b/vg_build.h @@ -4,86 +4,131 @@ #include #include #include +#include -#include "vg.h" #include "vg_opt.h" #include "vg_log.h" +#include "vg_string.h" /* we dont free dynamic vg_strs in this program. so, we dont care.. */ const char *__asan_default_options() { return "detect_leaks=0"; } -struct { - vg_str include, - library, - link, - sources, - dest, - project_name; +struct vg_env +{ + u32 optimization; - u32 optimization, - warnings; bool fresh, - debug_asan, - build_times; + debug_asan; - enum platform { + enum platform + { k_platform_anyplatform, k_platform_windows, k_platform_linux } platform; - enum architecture { + enum architecture + { k_architecture_anyarch, k_architecture_i386, k_architecture_x86_64, } arch; - enum compiler { + enum compiler + { k_compiler_blob, k_compiler_clang, k_compiler_zigcc } compiler; - enum libc_version { + enum libc_version + { k_libc_version_native, k_libc_version_2_23, } libc; -} -static vg_build = { .debug_asan = 1 }; +}; + +struct vg_env vg_test_env = { + .arch = k_architecture_x86_64, + .compiler = k_compiler_clang, + .libc = k_libc_version_native, + .debug_asan = 1, + .fresh = 0, + .platform = k_platform_linux, + .optimization = 0 +}; + +struct vg_env vg_release_env = { + .arch = k_architecture_x86_64, + .compiler = k_compiler_zigcc, + .libc = k_libc_version_2_23, + .fresh = 1, + .optimization = 3, + .platform = k_platform_anyplatform, + .debug_asan = 0 +}; + +struct vg_project +{ + struct vg_env *env; + + vg_str include, /* -I */ + library, /* -L */ + link, /* -llibrary */ + sources, /* file.c obj.o */ + uid, /* env/project identifier */ + target, /* result object name */ + + /* generated */ + compiled_objects; /* space seperated paths to compiled objects */ + + enum obj_type { + k_obj_type_none, + k_obj_type_exe, + k_obj_type_obj, + k_obj_type_shared, + } + type; +}; /* * string tables * -------------------------------------------------------------------------- */ -static const char *platform_names[] = { +static const char *platform_names[] = +{ [k_platform_anyplatform] = "anyplatform", [k_platform_windows] = "windows", [k_platform_linux] = "linux" }; -static const char *architecture_names[] = { +static const char *architecture_names[] = +{ [k_architecture_anyarch] = "anyarch", [k_architecture_i386] = "i386", [k_architecture_x86_64] = "x86_64" }; -static const char *compiler_names[] = { +static const char *compiler_names[] = +{ [k_compiler_blob] = "blob", [k_compiler_clang] = "clang", [k_compiler_zigcc] = "zig-cc" }; -static const char *compiler_paths[] = { +static const char *compiler_paths[] = +{ [k_compiler_blob] = NULL, [k_compiler_clang] = "clang", [k_compiler_zigcc] = "zig cc" }; -static const char *libc_names[] = { +static const char *libc_names[] = +{ [k_libc_version_native] = "", [k_libc_version_2_23] = ".2.23" }; @@ -92,30 +137,47 @@ static const char *libc_names[] = { * source specification * -------------------------------------------------------------------------- */ -void vg_add_source( const char *source ){ - vg_strcat( &vg_build.sources, source ); - vg_strcat( &vg_build.sources, " " ); +void vg_add_source( struct vg_project *proj, const char *source ) +{ + if( proj->type == k_obj_type_none ) + vg_fatal_error( "Cannot add source code without setting binary type\n" ); + + vg_strcat( &proj->sources, source ); + vg_strcat( &proj->sources, " " ); } -void vg_include_dir( const char *dir ){ - vg_strcat( &vg_build.include, dir ); - vg_strcat( &vg_build.include, " " ); +void vg_include_dir( struct vg_project *proj, const char *dir ) +{ + if( proj->type == k_obj_type_none ) + vg_fatal_error( "Cannot add include dir without setting binary type\n" ); + + vg_strcat( &proj->include, dir ); + vg_strcat( &proj->include, " " ); } -void vg_library_dir( const char *dir ){ - vg_strcat( &vg_build.library, dir ); - vg_strcat( &vg_build.library, " " ); +void vg_library_dir( struct vg_project *proj, const char *dir ) +{ + if( proj->type == k_obj_type_none ) + vg_fatal_error( "Cannot add library dir without setting binary type\n" ); + + vg_strcat( &proj->library, dir ); + vg_strcat( &proj->library, " " ); } -void vg_link( const char *lib ){ - vg_strcat( &vg_build.link, lib ); +void vg_link( struct vg_project *proj, const char *lib ) +{ + if( proj->type == k_obj_type_none ) + vg_fatal_error( "Cannot link library without setting binary type\n" ); + + vg_strcat( &proj->link, lib ); } /* * OS & file tools * -------------------------------------------------------------------------- */ -void vg_syscall( const char *fmt, ... ){ +void vg_syscall( const char *fmt, ... ) +{ va_list args; va_start( args, fmt ); @@ -128,139 +190,152 @@ void vg_syscall( const char *fmt, ... ){ exit(1); } -void vg_add_blob( const char *blob, const char *dest ){ - vg_syscall( "cp %s bin/%s/%s", blob, vg_build.project_name.buffer, dest ); +void vg_add_blob( struct vg_project *proj, const char *blob, const char *dest ) +{ + vg_syscall( "cp %s bin/%s/%s", blob, proj->uid.buffer, dest ); } -void vg_symlink( const char *folder, const char *bin_name ){ +void vg_symlink( struct vg_project *proj, + const char *folder, const char *bin_name ) +{ char dest[512]; - snprintf( dest, 512, "bin/%s/%s", vg_build.project_name.buffer, bin_name ); + snprintf( dest, 512, "bin/%s/%s", proj->uid.buffer, bin_name ); if( !access( dest, F_OK ) ) vg_syscall( "unlink %s", dest ); vg_syscall( "ln -srf %s %s", folder, dest ); } -void vg_tarball_last_project(void){ +void vg_tarball_project( struct vg_project *proj ) +{ vg_syscall( "tar -chzvf dist/%s-%u.tar.gz bin/%s/", - vg_build.project_name.buffer, time(NULL), - vg_build.project_name.buffer ); + proj->uid.buffer, time(NULL), proj->uid.buffer ); } - /* - * Standard VG includes & libraries which we use for games/graphics + * The project configurator and compiler. * -------------------------------------------------------------------------- */ -void vg_add_graphics(void){ - vg_add_source( "vg/dep/glad/glad.c" ); - - if( vg_build.platform == k_platform_windows ) - vg_add_blob( "vg/dep/sdl/SDL2.dll", "" ); - - vg_link( "-lm " ); - - if( vg_build.platform == k_platform_linux ) - vg_link( "-lSDL2 -lGL -lX11 -lXxf86vm -lXrandr -lXi -ldl -pthread " ); - else - vg_link( "-lmingw32 -lSDL2main -lSDL2 -lopengl32 \\\n" ); -} +void vg_project_new_target( struct vg_project *proj, const char *name, + enum obj_type type ) +{ + proj->type = type; + + vg_strnull( &proj->include, NULL, -1 ); + vg_strnull( &proj->library, NULL, -1 ); + vg_strnull( &proj->link, NULL, -1 ); + vg_strnull( &proj->sources, NULL, -1 ); + vg_strnull( &proj->target, NULL, -1 ); + + /* + * Setup target with appropriate extension + */ + vg_strcat( &proj->target, name ); + + if( proj->env->platform == k_platform_windows ) + { + if( type == k_obj_type_exe ) + vg_strcat( &proj->target, ".exe" ); + else if( type == k_obj_type_shared ) + vg_strcat( &proj->target, ".dll" ); + else if( type == k_obj_type_obj ) + vg_strcat( &proj->target, ".obj" ); + } -void vg_add_game_stuff(void){ - vg_add_blob( "vg/submodules/SDL_GameControllerDB/gamecontrollerdb.txt", "" ); - if( vg_build.platform == k_platform_linux ){ - vg_add_blob( "vg/dep/steam/libsteam_api.so", "" ); - vg_link( "-lsteam_api " ); + if( proj->env->platform == k_platform_linux ) + { + if( type == k_obj_type_shared ) + vg_strcat( &proj->target, ".so" ); + else if( type == k_obj_type_obj ) + vg_strcat( &proj->target, ".o" ); } - else if( vg_build.platform == k_platform_windows ){ - vg_add_blob( "vg/dep/steam/steam_api64.dll", "" ); - vg_link( "vg/dep/steam/steam_api64.dll " ); - vg_link( "vg/dep/sdl/SDL2.dll " ); - vg_library_dir( "-L./vg/dep/sdl " ); + + /* + * Add some regular includes / library dirs + */ + if( type != k_obj_type_none ) + { + vg_include_dir( proj, "-I." ); + vg_include_dir( proj, "-I./vg" ); + vg_library_dir( proj, "-L." ); + vg_library_dir( proj, "-L/usr/lib" ); } - vg_include_dir( "-I./vg/dep " ); - vg_library_dir( "-L./vg/dep/steam " ); + vg_info( " New target: %s\n", name ); } -/* - * The project configurator and compiler. - * -------------------------------------------------------------------------- */ +void vg_project_init( struct vg_project *proj, + struct vg_env *env, + const char *identifier ) +{ + proj->env = env; + proj->type = k_obj_type_none; -void vg_build_new( const char *name ){ - vg_strnull( &vg_build.include, NULL, -1 ); - vg_strnull( &vg_build.library, NULL, -1 ); - vg_strnull( &vg_build.link, NULL, -1 ); - vg_strnull( &vg_build.sources, NULL, -1 ); - vg_strnull( &vg_build.dest, NULL, -1 ); - vg_strnull( &vg_build.project_name, NULL, -1 ); + vg_strnull( &proj->uid, NULL, -1 ); + vg_strnull( &proj->compiled_objects, NULL, -1 ); /* check for problems in configuration */ - if( vg_build.libc != k_libc_version_native ){ - if( vg_build.compiler != k_compiler_zigcc ){ - vg_build.warnings ++; - vg_warn( "Cannot specify libc version using the '%s' compiler.\n", - compiler_names[ vg_build.compiler ] ); + if( env->libc != k_libc_version_native ){ + if( env->compiler != k_compiler_zigcc ){ + vg_fatal_error( + "Cannot specify libc version using the '%s' compiler.\n", + compiler_names[ env->compiler ] ); } } - if( vg_build.compiler == k_compiler_clang ){ - if( vg_build.platform != k_platform_linux ){ - vg_build.warnings ++; - vg_warn( "Cannot compile for '%s' using the '%s' compiler;" ); + if( env->compiler == k_compiler_clang ){ + if( env->platform != k_platform_linux ){ + vg_fatal_error( "Cannot compile for '%s' using the '%s' compiler;" ); } } - vg_str *proj = &vg_build.project_name; - - vg_strcat( proj, name ); - vg_strcatch( proj, '-' ); - vg_strcat( proj, platform_names[ vg_build.platform ] ); - vg_strcatch( proj, '-' ); - vg_strcat( proj, architecture_names[ vg_build.arch ] ); - vg_strcatch( proj, '-' ); - vg_strcat( proj, compiler_names[ vg_build.compiler ] ); - - vg_info( "project: %s (%s, %s, compiler: %s, opt:%u, fresh: %s)\n", - name, - platform_names[vg_build.platform], - architecture_names[vg_build.arch], - compiler_names[vg_build.compiler], - vg_build.optimization, - vg_build.fresh? "yes":"no"); - - if( proj->i < 3 ) - vg_fatal_error( "failed to create output directory\n" ); - - if( vg_build.fresh ) - vg_syscall( "rm -rf bin/%s", proj->buffer ); - vg_syscall( "mkdir -p bin/%s", proj->buffer ); - - vg_include_dir( "-I." ); - vg_include_dir( "-I./vg" ); - vg_library_dir( "-L." ); - vg_library_dir( "-L/usr/lib" ); + vg_strcat( &proj->uid, identifier ); + vg_strcatch( &proj->uid, '-' ); + vg_strcat( &proj->uid, platform_names[ env->platform ] ); + vg_strcatch( &proj->uid, '-' ); + vg_strcat( &proj->uid, architecture_names[ env->arch ] ); + vg_strcatch( &proj->uid, '-' ); + vg_strcat( &proj->uid, compiler_names[ env->compiler ] ); + + if( proj->uid.i < 3 ) + vg_fatal_error( "failed to create project UID\n" ); + + vg_info( "project_init: %s (%s, %s, compiler: %s, opt:%u, fresh: %s)\n", + identifier, + platform_names[env->platform], + architecture_names[env->arch], + compiler_names[env->compiler], + env->optimization, + env->fresh? "yes":"no"); + + if( env->fresh ) + vg_syscall( "rm -rf bin/%s", proj->uid.buffer ); + vg_syscall( "mkdir -p bin/%s", proj->uid.buffer ); } -void vg_compile( const char *name ){ +void vg_compile_project( struct vg_project *proj ) +{ vg_str cmd; vg_strnull( &cmd, NULL, -1 ); /* compiler specification */ vg_strcat( &cmd, "ccache " ); - vg_strcat( &cmd, compiler_paths[ vg_build.compiler ] ); + vg_strcat( &cmd, compiler_paths[ proj->env->compiler ] ); vg_strcat( &cmd, " -std=gnu99 -D_REENTRANT \\\n" ); - if( vg_build.optimization ){ + if( proj->env->optimization ) + { vg_strcat( &cmd, " -O" ); - vg_strcati32( &cmd, vg_build.optimization ); - vg_strcat( &cmd, " -DVG_RELEASE\\\n" ); + vg_strcati32( &cmd, proj->env->optimization ); + vg_strcat( &cmd, " -flto \\\n" ); } - else { + else + { /* add debugger / asan information */ vg_strcat( &cmd, " -O0 -ggdb3 -fno-omit-frame-pointer " ); - if( (vg_build.compiler == k_compiler_clang) && vg_build.debug_asan ){ + if( (proj->env->compiler == k_compiler_clang) && proj->env->debug_asan ) + { vg_strcat( &cmd, " -rdynamic -fsanitize=address -fPIE " "-fstack-protector-strong " ); } @@ -270,59 +345,200 @@ void vg_compile( const char *name ){ /* want a lot of warnings but not useless ones */ vg_strcat( &cmd, " -Wall -ferror-limit=8\\\n" - " -Wno-unused-function -Wno-unused-variable -Wno-format-truncation\\\n" + " -Wno-unused-function -Wno-unused-variable\\\n" " -Wno-unused-command-line-argument -Wno-unused-but-set-variable\\\n" ); + if( proj->env->compiler != k_compiler_clang ) + vg_strcat( &cmd, " -Wno-format-truncation\\\n" ); + /* include paths */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, vg_build.include.buffer ); + vg_strcat( &cmd, proj->include.buffer ); vg_strcat( &cmd, "\\\n" ); /* library paths */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, vg_build.library.buffer ); + vg_strcat( &cmd, proj->library.buffer ); vg_strcat( &cmd, "\\\n" ); /* sources */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, vg_build.sources.buffer ); + + if( proj->type == k_obj_type_obj ) + vg_strcat( &cmd, "-c " ); + + if( proj->type == k_obj_type_shared ) + vg_strcat( &cmd, "-shared -fPIC " ); + + vg_strcat( &cmd, proj->sources.buffer ); vg_strcat( &cmd, "\\\n" ); /* output */ vg_strcat( &cmd, " -o bin/" ); - vg_strcat( &cmd, vg_build.project_name.buffer ); + vg_strcat( &cmd, proj->uid.buffer ); vg_strcat( &cmd, "/" ); - vg_strcat( &cmd, name ); - if( vg_build.platform == k_platform_windows ) - vg_strcat( &cmd, ".exe" ); + vg_strcat( &cmd, proj->target.buffer ); vg_strcat( &cmd, "\\\n" ); /* link */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, vg_build.link.buffer ); + vg_strcat( &cmd, proj->link.buffer ); vg_strcat( &cmd, "\\\n" ); - /* dont remember what this does */ - vg_strcat( &cmd, " -Wl,-rpath=./\\\n" ); + if( proj->type == k_obj_type_exe ) + { + vg_strcat( &cmd, " -Wl,-rpath=./\\\n" ); + } /* target platform specification (zig-cc only) */ - if( vg_build.compiler == k_compiler_zigcc ){ + if( proj->env->compiler == k_compiler_zigcc ){ vg_strcat( &cmd, " -target " ); - vg_strcat( &cmd, architecture_names[vg_build.arch] ); + vg_strcat( &cmd, architecture_names[proj->env->arch] ); vg_strcat( &cmd, "-" ); - vg_strcat( &cmd, platform_names[vg_build.platform] ); + vg_strcat( &cmd, platform_names[proj->env->platform] ); - if( vg_build.platform == k_platform_linux ){ + if( proj->env->platform == k_platform_linux ){ vg_strcat( &cmd, "-gnu" ); - vg_strcat( &cmd, libc_names[vg_build.libc] ); + vg_strcat( &cmd, libc_names[proj->env->libc] ); } - if( vg_build.platform == k_platform_windows ){ + if( proj->env->platform == k_platform_windows ) + { /* we currently dont want pdb pretty much ever. goodbye! */ - vg_strcat( &cmd, " /SUBSYSTEM:windows /pdb:/dev/null" ); + + if( proj->type == k_obj_type_exe ) + { + vg_strcat( &cmd, " /pdb:/dev/null" ); + vg_strcat( &cmd, " /SUBSYSTEM:windows" ); + } } } vg_syscall( cmd.buffer ); + + /* add to results */ + vg_strcat( &proj->compiled_objects, "bin/" ); + vg_strcat( &proj->compiled_objects, proj->uid.buffer ); + vg_strcat( &proj->compiled_objects, "/" ); + vg_strcat( &proj->compiled_objects, proj->target.buffer ); + vg_strcat( &proj->compiled_objects, " \\\n " ); +} + +/* + * Standard VG includes & libraries which we use for games/graphics + * -------------------------------------------------------------------------- */ + +struct vg_engine_config +{ + bool use_3d, legacy_support_vg_msg1, log_source_info, steam_api, + custom_game_settings, + release_mode; + i32 fixed_update_hz; +} +vg_engine_default_config = { + .use_3d = 1, + .fixed_update_hz = 60, + .legacy_support_vg_msg1 = 0, + .log_source_info = 1, + .steam_api = 0, + .custom_game_settings = 0, + .release_mode = 0 +}; + +void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) +{ + if( !config ) config = &vg_engine_default_config; + vg_str config_string; + vg_strnull( &config_string, NULL, -1 ); + vg_strcat( &config_string, config->use_3d? "-DVG_3D \\\n": "-DVG_2D \\\n" ); + vg_strcat( &config_string, "-DVG_TIMESTEP_FIXED=\"(1.0/" ); + vg_strcati32( &config_string, config->fixed_update_hz ); + vg_strcat( &config_string, ".0)\" \\\n" ); + if( config->legacy_support_vg_msg1 ) + vg_strcat( &config_string, "-DVG_MSG_V1_SUPPORT \\\n" ); + if( config->log_source_info ) + vg_strcat( &config_string, "-DVG_LOG_SOURCE_INFO \\\n" ); + if( config->custom_game_settings ) + vg_strcat( &config_string, "-DVG_GAME_SETTINGS \\\n" ); + if( config->custom_game_settings ) + vg_strcat( &config_string, "-DVG_RELEASE \\\n" ); + + vg_strcat( &config_string, "\\\n" ); + + /* compile heavy dependencies seperately */ + struct vg_project dep_proj; + struct vg_env env = *proj->env; + env.optimization = 3; + env.debug_asan = 0; + + vg_project_init( &dep_proj, proj->env, "vg" ); + + /* external dependencies */ + vg_project_new_target( &dep_proj, "vg_deps", k_obj_type_obj ); + vg_add_source( &dep_proj, "vg/vg_depencies.c" ); + vg_compile_project( &dep_proj ); + + /* glad */ + vg_project_new_target( &dep_proj, "vg_glad", k_obj_type_obj ); + vg_add_source( &dep_proj, "vg/dep/glad/glad.c" ); + vg_include_dir( &dep_proj, "-I./vg/dep " ); + vg_compile_project( &dep_proj ); + + /* core engine */ + vg_project_new_target( &dep_proj, "vg_engine_core", k_obj_type_obj ); + vg_add_source( &dep_proj, config_string.buffer ); + vg_add_source( &dep_proj, "vg/vg_engine.c" ); + vg_include_dir( &dep_proj, "-I./vg/dep " ); + vg_compile_project( &dep_proj ); + + /* steamworks */ + if( config->steam_api ) + { + vg_project_new_target( &dep_proj, "vg_steam", k_obj_type_obj ); + vg_add_source( &dep_proj, "vg/vg_steam.c" ); + vg_compile_project( &dep_proj ); + + if( proj->env->platform == k_platform_linux ) + { + vg_add_blob( proj, "vg/dep/steam/libsteam_api.so", "" ); + vg_link( proj, "-lsteam_api " ); + } + else if( proj->env->platform == k_platform_windows ) + { + vg_add_blob( proj, "vg/dep/steam/steam_api64.dll", "" ); + vg_link( proj, "vg/dep/steam/steam_api64.dll " ); + } + + vg_library_dir( proj, "-L./vg/dep/steam " ); + vg_include_dir( proj, "-I./vg/dep " ); + } + + /* precipitate to the client project */ + + vg_link( proj, "-lm " ); + if( proj->env->platform == k_platform_linux ) + { + vg_link( proj, "-lSDL2 -lGL -lX11 -lXxf86vm " + "-lXrandr -lXi -ldl -pthread " ); + } + else if( proj->env->platform == k_platform_windows ) + { + vg_link( proj, "-lSDL2main -lSDL2 -lopengl32 \\\n" ); + vg_link( proj, "vg/dep/sdl/SDL2.dll " ); + vg_add_blob( proj, "vg/dep/sdl/SDL2.dll ", "" ); + vg_library_dir( proj, "-L./vg/dep/sdl " ); + } + + vg_add_source( proj, config_string.buffer ); + vg_add_source( proj, dep_proj.compiled_objects.buffer ); + vg_add_source( proj, "\\\n" ); + vg_include_dir( proj, "-I./vg/dep " ); + vg_link( proj, "-lm " ); +} + +void vg_add_controller_database( struct vg_project *proj ) +{ + vg_add_blob( proj, + "vg/submodules/SDL_GameControllerDB/gamecontrollerdb.txt", "" ); } diff --git a/vg_build_utils_shader.h b/vg_build_utils_shader.h index d1d2d05..5747cf9 100644 --- a/vg_build_utils_shader.h +++ b/vg_build_utils_shader.h @@ -1,9 +1,3 @@ -#include "vg.h" - -#ifdef VG_GAME - #error ! -#endif - #define STB_INCLUDE_IMPLEMENTATION #define STB_INCLUDE_LINE_GLSL #include "submodules/stb/stb_include.h" diff --git a/vg_bvh.c b/vg_bvh.c new file mode 100644 index 0000000..bf1ffc4 --- /dev/null +++ b/vg_bvh.c @@ -0,0 +1,301 @@ +#include "vg_bvh.h" +#include "vg_mem.h" +#include "vg_m.h" +#include "vg_lines.h" +#include "vg_log.h" + +void bh_update_bounds( bh_tree *bh, u32 inode ){ + bh_node *node = &bh->nodes[ inode ]; + + box_init_inf( node->bbx ); + for( u32 i=0; icount; i++ ){ + u32 idx = node->start+i; + bh->system->expand_bound( bh->user, node->bbx, idx ); + } +} + +void bh_subdivide( bh_tree *bh, u32 inode ){ + bh_node *node = &bh->nodes[ inode ]; + + if( node->count <= bh->max_per_leaf ) + return; + + v3f extent; + v3_sub( node->bbx[1], node->bbx[0], extent ); + + int axis = 0; + if( extent[1] > extent[0] ) axis = 1; + if( extent[2] > extent[axis] ) axis = 2; + + float split = node->bbx[0][axis] + extent[axis]*0.5f; + float avg = 0.0; + for( u32 t=0; tcount; t++ ){ + u32 idx = node->start+t; + avg += bh->system->item_centroid( bh->user, idx, axis ); + } + avg /= (float)node->count; + split = avg; + + + i32 i = node->start, + j = i + node->count-1; + + while( i <= j ){ + f32 centroid = bh->system->item_centroid( bh->user, i, axis ); + + if( centroid < split ) + i ++; + else{ + bh->system->item_swap( bh->user, i, j ); + j --; + } + } + + u32 left_count = i - node->start; + if( left_count == 0 || left_count == node->count ) return; + + u32 il = bh->node_count ++, + ir = bh->node_count ++; + + bh_node *lnode = &bh->nodes[il], + *rnode = &bh->nodes[ir]; + + lnode->start = node->start; + lnode->count = left_count; + rnode->start = i; + rnode->count = node->count - left_count; + + node->il = il; + node->ir = ir; + node->count = 0; + + bh_update_bounds( bh, il ); + bh_update_bounds( bh, ir ); + bh_subdivide( bh, il ); + bh_subdivide( bh, ir ); +} + +void bh_rebuild( bh_tree *bh, u32 item_count ) +{ + bh_node *root = &bh->nodes[0]; + bh->node_count = 1; + + root->il = 0; + root->ir = 0; + root->count = item_count; + root->start = 0; + + bh_update_bounds( bh, 0 ); + + if( item_count > 2 ) + bh_subdivide( bh, 0 ); +} + +bh_tree *bh_create( void *lin_alloc, bh_system *system, + void *user, u32 item_count, u32 max_per_leaf ) +{ + u32 alloc_count = VG_MAX( 1, item_count ); + + u32 totsize = sizeof(bh_tree) + sizeof(bh_node)*(alloc_count*2-1); + bh_tree *bh = lin_alloc? vg_linear_alloc( lin_alloc, vg_align8(totsize) ): + malloc( totsize ); + bh->system = system; + bh->user = user; + bh->max_per_leaf = max_per_leaf; + bh_rebuild( bh, item_count ); + + if( lin_alloc ){ + totsize = vg_align8(sizeof(bh_tree) + sizeof(bh_node) * bh->node_count); + bh = vg_linear_resize( lin_alloc, bh, totsize ); + } + + vg_success( "BVH done, size: %u/%u\n", bh->node_count, (alloc_count*2-1) ); + return bh; +} + +/* + * Draw items in this leaf node. + * *item_debug() must be set! + */ +void bh_debug_leaf( bh_tree *bh, bh_node *node ) +{ + vg_line_boxf( node->bbx, 0xff00ff00 ); + + if( bh->system->item_debug ){ + for( u32 i=0; icount; i++ ){ + u32 idx = node->start+i; + bh->system->item_debug( bh->user, idx ); + } + } +} + +/* + * Trace the bh tree all the way down to the leaf nodes where pos is inside + */ +void bh_debug_trace( bh_tree *bh, u32 inode, v3f pos, u32 colour ) +{ + bh_node *node = &bh->nodes[ inode ]; + + if( (pos[0] >= node->bbx[0][0] && pos[0] <= node->bbx[1][0]) && + (pos[2] >= node->bbx[0][2] && pos[2] <= node->bbx[1][2]) ) + { + if( !node->count ){ + vg_line_boxf( node->bbx, colour ); + + bh_debug_trace( bh, node->il, pos, colour ); + bh_debug_trace( bh, node->ir, pos, colour ); + } + else{ + if( bh->system->item_debug ) + bh_debug_leaf( bh, node ); + } + } +} + +void bh_iter_init_generic( i32 root, bh_iter *it ) +{ + it->stack[0].id = root; + it->stack[0].depth = 0; + it->depth = 0; + it->i = 0; +} + +void bh_iter_init_box( i32 root, bh_iter *it, boxf box ) +{ + bh_iter_init_generic( root, it ); + it->query = k_bh_query_box; + + box_copy( box, it->box.box ); +} + +void bh_iter_init_ray( i32 root, bh_iter *it, v3f co, v3f dir, f32 max_dist ) +{ + bh_iter_init_generic( root, it ); + it->query = k_bh_query_ray; + + v3_div( (v3f){1.0f,1.0f,1.0f}, dir, it->ray.inv_dir ); + v3_copy( co, it->ray.co ); + it->ray.max_dist = max_dist; +} + +void bh_iter_init_range( i32 root, bh_iter *it, v3f co, f32 range ) +{ + bh_iter_init_generic( root, it ); + it->query = k_bh_query_range; + + v3_copy( co, it->range.co ); + it->range.dist_sqr = range*range; +} + +/* NOTE: does not compute anything beyond the leaf level. element level tests + * should be implemented by the users code. + * + * this is like a 'broad phase only' deal. + */ +i32 bh_next( bh_tree *bh, bh_iter *it, i32 *em ) +{ + while( it->depth >= 0 ){ + bh_node *inode = &bh->nodes[ it->stack[it->depth].id ]; + + /* Only process overlapping nodes */ + i32 q = 0; + + if( it->i ) /* already checked */ + q = 1; + else{ + if( it->query == k_bh_query_box ) + q = box_overlap( inode->bbx, it->box.box ); + else if( it->query == k_bh_query_ray ) + q = ray_aabb1( inode->bbx, it->ray.co, + it->ray.inv_dir, it->ray.max_dist ); + else { + v3f nearest; + closest_point_aabb( it->range.co, inode->bbx, nearest ); + + if( v3_dist2( nearest, it->range.co ) <= it->range.dist_sqr ) + q = 1; + } + } + + if( !q ){ + it->depth --; + continue; + } + + if( inode->count ){ + if( it->i < inode->count ){ + *em = inode->start+it->i; + it->i ++; + return 1; + } + else{ + it->depth --; + it->i = 0; + } + } + else{ + if( it->depth+1 >= vg_list_size(it->stack) ){ + vg_error( "Maximum stack reached!\n" ); + return 0; + } + + it->stack[it->depth ].id = inode->il; + it->stack[it->depth+1].id = inode->ir; + it->depth ++; + it->i = 0; + } + } + + return 0; +} + +int bh_closest_point( bh_tree *bh, v3f pos, v3f closest, float max_dist ) +{ + if( bh->node_count < 2 ) + return -1; + + max_dist = max_dist*max_dist; + + int queue[ 128 ], + depth = 0, + best_item = -1; + + queue[0] = 0; + + while( depth >= 0 ){ + bh_node *inode = &bh->nodes[ queue[depth] ]; + + v3f p1; + closest_point_aabb( pos, inode->bbx, p1 ); + + /* branch into node if its closer than current best */ + float node_dist = v3_dist2( pos, p1 ); + if( node_dist < max_dist ){ + if( inode->count ){ + for( int i=0; icount; i++ ){ + v3f p2; + bh->system->item_closest( bh->user, inode->start+i, pos, p2 ); + + float item_dist = v3_dist2( pos, p2 ); + if( item_dist < max_dist ){ + max_dist = item_dist; + v3_copy( p2, closest ); + best_item = inode->start+i; + } + } + + depth --; + } + else{ + queue[depth] = inode->il; + queue[depth+1] = inode->ir; + + depth ++; + } + } + else + depth --; + } + + return best_item; +} diff --git a/vg_bvh.h b/vg_bvh.h index 85790c5..37b100a 100644 --- a/vg_bvh.h +++ b/vg_bvh.h @@ -47,6 +47,16 @@ struct bh_tree{ nodes[]; }; +/* + * TODO: This might get ugly, but it would make sense to do codegen here + * + * eg.. #define BH_SYSTEM( FN_EXPAND, FN_CENTROID, FN_CLOSEST ... ) + * + * which macros out variants of the functions so we can give the compier + * a more solid thing to work with without function pointers inside + * hot loops. + */ + struct bh_system{ void (*expand_bound)( void *user, boxf bound, u32 item_index ); float (*item_centroid)( void *user, u32 item_index, int axis ); @@ -64,174 +74,19 @@ struct bh_system{ int (*cast_ray)( void *user, u32 index, v3f co, v3f dir, ray_hit *hit ); }; -#define BVH_FIXED_MODE -#ifdef BVH_FIXED_MODE -static f32 shape_bvh_centroid( void *user, u32 item_index, int axis ); -static void shape_bvh_swap( void *user, u32 ia, u32 ib ); -static void shape_bvh_expand_bound( void *user, boxf bound, u32 item_index ); -#endif - -static void bh_update_bounds( bh_tree *bh, u32 inode ){ - bh_node *node = &bh->nodes[ inode ]; - - box_init_inf( node->bbx ); - for( u32 i=0; icount; i++ ){ - u32 idx = node->start+i; -#ifdef BVH_FIXED_MODE - shape_bvh_expand_bound( bh->user, node->bbx, idx ); -#else - bh->system->expand_bound( bh->user, node->bbx, idx ); -#endif - } -} - -static void bh_subdivide( bh_tree *bh, u32 inode ){ - bh_node *node = &bh->nodes[ inode ]; - - if( node->count <= bh->max_per_leaf ) - return; - - v3f extent; - v3_sub( node->bbx[1], node->bbx[0], extent ); - - int axis = 0; - if( extent[1] > extent[0] ) axis = 1; - if( extent[2] > extent[axis] ) axis = 2; - - float split = node->bbx[0][axis] + extent[axis]*0.5f; - float avg = 0.0; - for( u32 t=0; tcount; t++ ){ - u32 idx = node->start+t; -#ifdef BVH_FIXED_MODE - avg += shape_bvh_centroid( bh->user, idx, axis ); -#else - avg += bh->system->item_centroid( bh->user, idx, axis ); -#endif - } - avg /= (float)node->count; - split = avg; - - - i32 i = node->start, - j = i + node->count-1; - - while( i <= j ){ -#ifdef BVH_FIXED_MODE - f32 centroid = shape_bvh_centroid( bh->user, i, axis ); -#else - f32 centroid = bh->system->item_centroid( bh->user, i, axis ); -#endif - - if( centroid < split ) - i ++; - else{ -#ifdef BVH_FIXED_MODE - shape_bvh_swap( bh->user, i, j ); -#else - bh->system->item_swap( bh->user, i, j ); -#endif - j --; - } - } - - u32 left_count = i - node->start; - if( left_count == 0 || left_count == node->count ) return; - - u32 il = bh->node_count ++, - ir = bh->node_count ++; - - bh_node *lnode = &bh->nodes[il], - *rnode = &bh->nodes[ir]; - - lnode->start = node->start; - lnode->count = left_count; - rnode->start = i; - rnode->count = node->count - left_count; - - node->il = il; - node->ir = ir; - node->count = 0; - - bh_update_bounds( bh, il ); - bh_update_bounds( bh, ir ); - bh_subdivide( bh, il ); - bh_subdivide( bh, ir ); -} - -static void bh_rebuild( bh_tree *bh, u32 item_count ){ - bh_node *root = &bh->nodes[0]; - bh->node_count = 1; - - root->il = 0; - root->ir = 0; - root->count = item_count; - root->start = 0; - - bh_update_bounds( bh, 0 ); - - if( item_count > 2 ) - bh_subdivide( bh, 0 ); -} - -static bh_tree *bh_create( void *lin_alloc, bh_system *system, - void *user, u32 item_count, u32 max_per_leaf ){ - assert( max_per_leaf > 0 ); +void bh_update_bounds( bh_tree *bh, u32 inode ); +void bh_subdivide( bh_tree *bh, u32 inode ); +void bh_rebuild( bh_tree *bh, u32 item_count ); +bh_tree *bh_create( void *lin_alloc, bh_system *system, + void *user, u32 item_count, u32 max_per_leaf ); - u32 alloc_count = VG_MAX( 1, item_count ); - u32 totsize = sizeof(bh_tree) + sizeof(bh_node)*(alloc_count*2-1); - bh_tree *bh = lin_alloc? vg_linear_alloc( lin_alloc, vg_align8(totsize) ): - malloc( totsize ); - bh->system = system; - bh->user = user; - bh->max_per_leaf = max_per_leaf; - bh_rebuild( bh, item_count ); - - if( lin_alloc ){ - totsize = vg_align8(sizeof(bh_tree) + sizeof(bh_node) * bh->node_count); - bh = vg_linear_resize( lin_alloc, bh, totsize ); - } - - vg_success( "BVH done, size: %u/%u\n", bh->node_count, (alloc_count*2-1) ); - return bh; -} - -/* - * Draw items in this leaf node. - * *item_debug() must be set! - */ -static void bh_debug_leaf( bh_tree *bh, bh_node *node ){ - vg_line_boxf( node->bbx, 0xff00ff00 ); - - if( bh->system->item_debug ){ - for( u32 i=0; icount; i++ ){ - u32 idx = node->start+i; - bh->system->item_debug( bh->user, idx ); - } - } -} +void bh_debug_leaf( bh_tree *bh, bh_node *node ); /* * Trace the bh tree all the way down to the leaf nodes where pos is inside */ -static void bh_debug_trace( bh_tree *bh, u32 inode, v3f pos, u32 colour ){ - bh_node *node = &bh->nodes[ inode ]; - - if( (pos[0] >= node->bbx[0][0] && pos[0] <= node->bbx[1][0]) && - (pos[2] >= node->bbx[0][2] && pos[2] <= node->bbx[1][2]) ) - { - if( !node->count ){ - vg_line_boxf( node->bbx, colour ); - - bh_debug_trace( bh, node->il, pos, colour ); - bh_debug_trace( bh, node->ir, pos, colour ); - } - else{ - if( bh->system->item_debug ) - bh_debug_leaf( bh, node ); - } - } -} +void bh_debug_trace( bh_tree *bh, u32 inode, v3f pos, u32 colour ); typedef struct bh_iter bh_iter; struct bh_iter{ @@ -269,147 +124,9 @@ struct bh_iter{ i32 depth, i; }; -static void bh_iter_init_generic( i32 root, bh_iter *it ){ - it->stack[0].id = root; - it->stack[0].depth = 0; - it->depth = 0; - it->i = 0; -} - -static void bh_iter_init_box( i32 root, bh_iter *it, boxf box ){ - bh_iter_init_generic( root, it ); - it->query = k_bh_query_box; - - box_copy( box, it->box.box ); -} - -static void bh_iter_init_ray( i32 root, bh_iter *it, v3f co, - v3f dir, f32 max_dist ){ - bh_iter_init_generic( root, it ); - it->query = k_bh_query_ray; - - v3_div( (v3f){1.0f,1.0f,1.0f}, dir, it->ray.inv_dir ); - v3_copy( co, it->ray.co ); - it->ray.max_dist = max_dist; -} - -static void bh_iter_init_range( i32 root, bh_iter *it, v3f co, f32 range ){ - bh_iter_init_generic( root, it ); - it->query = k_bh_query_range; - - v3_copy( co, it->range.co ); - it->range.dist_sqr = range*range; -} - -/* NOTE: does not compute anything beyond the leaf level. element level tests - * should be implemented by the users code. - * - * this is like a 'broad phase only' deal. - */ -static i32 bh_next( bh_tree *bh, bh_iter *it, i32 *em ){ - while( it->depth >= 0 ){ - bh_node *inode = &bh->nodes[ it->stack[it->depth].id ]; - - /* Only process overlapping nodes */ - i32 q = 0; - - if( it->i ) /* already checked */ - q = 1; - else{ - if( it->query == k_bh_query_box ) - q = box_overlap( inode->bbx, it->box.box ); - else if( it->query == k_bh_query_ray ) - q = ray_aabb1( inode->bbx, it->ray.co, - it->ray.inv_dir, it->ray.max_dist ); - else { - v3f nearest; - closest_point_aabb( it->range.co, inode->bbx, nearest ); - - if( v3_dist2( nearest, it->range.co ) <= it->range.dist_sqr ) - q = 1; - } - } - - if( !q ){ - it->depth --; - continue; - } - - if( inode->count ){ - if( it->i < inode->count ){ - *em = inode->start+it->i; - it->i ++; - return 1; - } - else{ - it->depth --; - it->i = 0; - } - } - else{ - if( it->depth+1 >= vg_list_size(it->stack) ){ - vg_error( "Maximum stack reached!\n" ); - return 0; - } - - it->stack[it->depth ].id = inode->il; - it->stack[it->depth+1].id = inode->ir; - it->depth ++; - it->i = 0; - } - } - - return 0; -} - -static int bh_closest_point( bh_tree *bh, v3f pos, - v3f closest, float max_dist ) -{ - if( bh->node_count < 2 ) - return -1; - - max_dist = max_dist*max_dist; - - int queue[ 128 ], - depth = 0, - best_item = -1; - - queue[0] = 0; - - while( depth >= 0 ){ - bh_node *inode = &bh->nodes[ queue[depth] ]; - - v3f p1; - closest_point_aabb( pos, inode->bbx, p1 ); - - /* branch into node if its closer than current best */ - float node_dist = v3_dist2( pos, p1 ); - if( node_dist < max_dist ){ - if( inode->count ){ - for( int i=0; icount; i++ ){ - v3f p2; - bh->system->item_closest( bh->user, inode->start+i, pos, p2 ); - - float item_dist = v3_dist2( pos, p2 ); - if( item_dist < max_dist ){ - max_dist = item_dist; - v3_copy( p2, closest ); - best_item = inode->start+i; - } - } - - depth --; - } - else{ - queue[depth] = inode->il; - queue[depth+1] = inode->ir; - - depth ++; - } - } - else - depth --; - } - - return best_item; -} +void bh_iter_init_generic( i32 root, bh_iter *it ); +void bh_iter_init_box( i32 root, bh_iter *it, boxf box ); +void bh_iter_init_ray( i32 root, bh_iter *it, v3f co, v3f dir, f32 max_dist ); +void bh_iter_init_range( i32 root, bh_iter *it, v3f co, f32 range ); +i32 bh_next( bh_tree *bh, bh_iter *it, i32 *em ); +int bh_closest_point( bh_tree *bh, v3f pos, v3f closest, float max_dist ); diff --git a/vg_camera.c b/vg_camera.c new file mode 100644 index 0000000..8cd261f --- /dev/null +++ b/vg_camera.c @@ -0,0 +1,80 @@ +#include "vg_camera.h" +#include "vg_m.h" +#include "vg_engine.h" + +void vg_camera_lerp_angles( v3f a, v3f b, f32 t, v3f d ) +{ + d[0] = vg_alerpf( a[0], b[0], t ); + d[1] = vg_lerpf( a[1], b[1], t ); + d[2] = vg_lerpf( a[2], b[2], t ); +} + +/* lerp position, fov, and angles */ +void vg_camera_lerp( vg_camera *a, vg_camera *b, f32 t, vg_camera *d ) +{ + v3_lerp( a->pos, b->pos, t, d->pos ); + vg_camera_lerp_angles( a->angles, b->angles, t, d->angles ); + d->fov = vg_lerpf( a->fov, b->fov, t ); +} + +void vg_camera_copy( vg_camera *a, vg_camera *d ) +{ + v3_copy( a->pos, d->pos ); + v3_copy( a->angles, d->angles ); + d->fov = a->fov; +} + +void vg_m4x3_transform_camera( m4x3f m, vg_camera *cam ) +{ + m4x3_mulv( m, cam->pos, cam->pos ); + + v3f v0; + v3_angles_vector( cam->angles, v0 ); + m3x3_mulv( m, v0, v0 ); + v3_normalize( v0 ); + v3_angles( v0, cam->angles ); +} + +/* + * 1) [angles, pos] -> transform + */ +void vg_camera_update_transform( vg_camera *cam ) +{ + v4f qyaw, qpitch, qcam; + q_axis_angle( qyaw, (v3f){ 0.0f, 1.0f, 0.0f }, -cam->angles[0] ); + q_axis_angle( qpitch, (v3f){ 1.0f, 0.0f, 0.0f }, -cam->angles[1] ); + + q_mul( qyaw, qpitch, qcam ); + q_m3x3( qcam, cam->transform ); + v3_copy( cam->pos, cam->transform[3] ); +} + +/* + * 2) [transform] -> transform_inverse, view matrix + */ +void vg_camera_update_view( vg_camera *cam ) +{ + m4x4_copy( cam->mtx.v, cam->mtx_prev.v ); + m4x3_invert_affine( cam->transform, cam->transform_inverse ); + m4x3_expand( cam->transform_inverse, cam->mtx.v ); +} + +/* + * 3) [fov,nearz,farz] -> projection matrix + */ +void vg_camera_update_projection( vg_camera *cam ) +{ + m4x4_copy( cam->mtx.p, cam->mtx_prev.p ); + m4x4_projection( cam->mtx.p, cam->fov, + (float)vg.window_x / (float)vg.window_y, + cam->nearz, cam->farz ); +} + +/* + * 4) [projection matrix, view matrix] -> previous pv, new pv + */ +void vg_camera_finalize( vg_camera *cam ) +{ + m4x4_copy( cam->mtx.pv, cam->mtx_prev.pv ); + m4x4_mul( cam->mtx.p, cam->mtx.v, cam->mtx.pv ); +} diff --git a/vg_camera.h b/vg_camera.h index 9fd660b..45d3444 100644 --- a/vg_camera.h +++ b/vg_camera.h @@ -1,8 +1,10 @@ #pragma once + #include "vg_m.h" typedef struct vg_camera vg_camera; -struct vg_camera{ +struct vg_camera +{ /* Input */ v3f angles; v3f pos; @@ -21,71 +23,29 @@ struct vg_camera{ mtx_prev; }; -static void vg_camera_lerp_angles( v3f a, v3f b, f32 t, v3f d ){ - d[0] = vg_alerpf( a[0], b[0], t ); - d[1] = vg_lerpf( a[1], b[1], t ); - d[2] = vg_lerpf( a[2], b[2], t ); -} +void vg_camera_lerp_angles( v3f a, v3f b, f32 t, v3f d ); /* lerp position, fov, and angles */ -static void vg_camera_lerp( vg_camera *a, vg_camera *b, f32 t, vg_camera *d ){ - v3_lerp( a->pos, b->pos, t, d->pos ); - vg_camera_lerp_angles( a->angles, b->angles, t, d->angles ); - d->fov = vg_lerpf( a->fov, b->fov, t ); -} - -static void vg_camera_copy( vg_camera *a, vg_camera *d ){ - v3_copy( a->pos, d->pos ); - v3_copy( a->angles, d->angles ); - d->fov = a->fov; -} - -static void vg_m4x3_transform_camera( m4x3f m, vg_camera *cam ){ - m4x3_mulv( m, cam->pos, cam->pos ); - - v3f v0; - v3_angles_vector( cam->angles, v0 ); - m3x3_mulv( m, v0, v0 ); - v3_normalize( v0 ); - v3_angles( v0, cam->angles ); -} +void vg_camera_lerp( vg_camera *a, vg_camera *b, f32 t, vg_camera *d ); +void vg_camera_copy( vg_camera *a, vg_camera *d ); +void vg_m4x3_transform_camera( m4x3f m, vg_camera *cam ); /* * 1) [angles, pos] -> transform */ -static void vg_camera_update_transform( vg_camera *cam ){ - v4f qyaw, qpitch, qcam; - q_axis_angle( qyaw, (v3f){ 0.0f, 1.0f, 0.0f }, -cam->angles[0] ); - q_axis_angle( qpitch, (v3f){ 1.0f, 0.0f, 0.0f }, -cam->angles[1] ); - - q_mul( qyaw, qpitch, qcam ); - q_m3x3( qcam, cam->transform ); - v3_copy( cam->pos, cam->transform[3] ); -} +void vg_camera_update_transform( vg_camera *cam ); /* * 2) [transform] -> transform_inverse, view matrix */ -static void vg_camera_update_view( vg_camera *cam ){ - m4x4_copy( cam->mtx.v, cam->mtx_prev.v ); - m4x3_invert_affine( cam->transform, cam->transform_inverse ); - m4x3_expand( cam->transform_inverse, cam->mtx.v ); -} +void vg_camera_update_view( vg_camera *cam ); /* * 3) [fov,nearz,farz] -> projection matrix */ -static void vg_camera_update_projection( vg_camera *cam ){ - m4x4_copy( cam->mtx.p, cam->mtx_prev.p ); - m4x4_projection( cam->mtx.p, cam->fov, - (float)vg.window_x / (float)vg.window_y, - cam->nearz, cam->farz ); -} +void vg_camera_update_projection( vg_camera *cam ); /* * 4) [projection matrix, view matrix] -> previous pv, new pv */ -static void vg_camera_finalize( vg_camera *cam ){ - m4x4_copy( cam->mtx.pv, cam->mtx_prev.pv ); - m4x4_mul( cam->mtx.p, cam->mtx.v, cam->mtx.pv ); -} +void vg_camera_finalize( vg_camera *cam ); diff --git a/vg_console.c b/vg_console.c new file mode 100644 index 0000000..ccc39f7 --- /dev/null +++ b/vg_console.c @@ -0,0 +1,666 @@ +#include "vg_engine.h" +#include "vg_console.h" +#include "vg_imgui.h" +#include "vg_log.h" +#include "vg_string.h" +#include + +struct vg_console vg_console; + +void vg_console_reg_var( const char *alias, void *ptr, enum vg_var_dtype type, + u32 flags ) +{ + if( vg_thread_purpose() == k_thread_purpose_main ) + vg_fatal_error( "FIXME: Cannot register VAR from main thread" ); + + if( vg_console.var_count > vg_list_size(vg_console.vars) ) + vg_fatal_error( "Too many vars registered" ); + + vg_var *var = &vg_console.vars[ vg_console.var_count ++ ]; + var->name = alias; + var->data = ptr; + var->data_type = type; + var->flags = flags; + + vg_info( "Console variable '%s' registered\n", alias ); +} + +void vg_console_reg_cmd( const char *alias, + int (*function)(int argc, const char *argv[]), + void (*poll_suggest)(int argc, const char *argv[]) ) +{ + if( vg_thread_purpose() == k_thread_purpose_main ) + vg_fatal_error( "FIXME: Cannot register CMD from main thread" ); + + if( vg_console.function_count > vg_list_size(vg_console.functions) ) + vg_fatal_error( "Too many functions registered" ); + + vg_cmd *cmd = &vg_console.functions[ vg_console.function_count ++ ]; + + cmd->function = function; + cmd->poll_suggest = poll_suggest; + cmd->name = alias; + + vg_info( "Console function '%s' registered\n", alias ); +} + +static int _vg_console_list( int argc, char const *argv[] ) +{ + for( int i=0; iname ); + } + + for( int i=0; idata_type], + cv->name ); + } + + return 0; +} + +static void vg_console_write_persistent(void) +{ + FILE *fp = fopen( "cfg/auto.conf", "w" ); + + if( !fp ){ + vg_error( "Cannot open cfg/auto.conf\n" ); + return; + } + + for( int i=0; iflags & VG_VAR_PERSISTENT ){ + if( cv->data_type == k_var_dtype_i32 ){ + fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) ); + } + else if( cv->data_type == k_var_dtype_u32 ){ + fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) ); + } + else if( cv->data_type == k_var_dtype_f32 ){ + fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) ); + } + else if( cv->data_type == k_var_dtype_str ){ + vg_str *str = cv->data; + if( str->buffer && (str->i > 0) ) + fprintf( fp, "%s %s\n", cv->name, str->buffer ); + } + } + } + + fclose( fp ); +} + +static void _vg_console_free(void) +{ + vg_console_write_persistent(); +} + +/* + * splits src into tokens and fills out args as pointers to those tokens + * returns number of tokens + * dst must be as long as src + */ +static int vg_console_tokenize( const char *src, char *dst, + const char *args[8] ) +{ + int arg_count = 0, + in_token = 0; + + for( int i=0;; i ++ ){ + if( src[i] ){ + if( src[i] == ' ' || src[i] == '\t' ){ + if( in_token ) + dst[i] = '\0'; + + in_token = 0; + + if( arg_count == 8 ) + break; + } + else{ + dst[i] = src[i]; + + if( !in_token ){ + args[ arg_count ++ ] = &dst[i]; + in_token = 1; + } + } + } + else{ + dst[i] = '\0'; + break; + } + } + + return arg_count; +} + +vg_var *vg_console_match_var( const char *kw ) +{ + for( int i=0; iname, kw ) ){ + return cv; + } + } + + return NULL; +} + +vg_cmd *vg_console_match_cmd( const char *kw ) +{ + for( int i=0; iname, kw ) ){ + return cmd; + } + } + + return NULL; +} + +void vg_execute_console_input( const char *cmd, bool silent ) +{ + char temp[512]; + char const *args[8]; + int arg_count = vg_console_tokenize( cmd, temp, args ); + + if( arg_count == 0 ) + return; + + vg_var *cv = vg_console_match_var( args[0] ); + vg_cmd *fn = vg_console_match_cmd( args[0] ); + + if( cv ){ + /* Cvar Matched, try get value */ + if( arg_count >= 2 ){ + if( cv->flags & VG_VAR_CHEAT ){ + if( !vg_console.cheats && !silent ){ + vg_error( "variable is cheat protected\n" ); + return; + } + } + + if( (cv->data_type == k_var_dtype_u32) || + (cv->data_type == k_var_dtype_i32) ){ + int *ptr = cv->data; + *ptr = atoi( args[1] ); + } + else if( cv->data_type == k_var_dtype_f32 ){ + float *ptr = cv->data; + *ptr = atof( args[1] ); + } + else if( cv->data_type == k_var_dtype_str ){ + vg_str *str = cv->data; + vg_strfree( str ); + vg_strnull( str, NULL, -1 ); + + for( int i=1; idata_type == k_var_dtype_i32 ) + vg_info( "= %d\n", *((int *)cv->data) ); + else if( cv->data_type == k_var_dtype_u32 ) + vg_info( "= %u\n", *((u32 *)cv->data) ); + else if( cv->data_type == k_var_dtype_f32 ) + vg_info( "= %.4f\n", *((float *)cv->data) ); + else if( cv->data_type == k_var_dtype_str ){ + vg_str *str = cv->data; + vg_info( "= '%s'\n", str->buffer ); + } + } + + return; + } + else if( fn ){ + fn->function( arg_count-1, args+1 ); + return; + } + + if( !silent ) + vg_error( "No command/var named '%s'. Use 'list' to view all\n",args[0]); +} + +u32 str_lev_distance( const char *s1, const char *s2 ){ + u32 m = strlen( s1 ), + n = strlen( s2 ); + + if( m==0 ) return n; + if( n==0 ) return m; + + u32 costs[ 256 ]; + + for( u32 k=0; k<=n; k++ ) + costs[k] = k; + + u32 i = 0; + for( u32 i=0; i=0; j -- ) + if( score > vg_console.suggestions[j].lev_score ) + best_pos = j; + + /* insert if good score */ + if( best_pos < vg_list_size( vg_console.suggestions ) ){ + int start = VG_MIN( vg_console.suggestion_count, + vg_list_size( vg_console.suggestions )-1 ); + for( int j=start; j>best_pos; j -- ) + vg_console.suggestions[j] = vg_console.suggestions[j-1]; + + vg_console.suggestions[ best_pos ].str = str; + vg_console.suggestions[ best_pos ].len = strlen( str ); + vg_console.suggestions[ best_pos ].lev_score = score; + + if( vg_console.suggestion_count < + vg_list_size( vg_console.suggestions ) ) + vg_console.suggestion_count ++; + } +} + +static void console_update_suggestions(void) +{ + if( vg_ui.focused_control_type != k_ui_control_textbox || + vg_ui.textbuf != vg_console.input ) + return; + + vg_console.suggestion_count = 0; + vg_console.suggestion_select = -1; + vg_console.suggestion_maxlen = 0; + + /* + * - must be typing something + * - must be at the end + * - prev char must not be a whitespace + * - cursors should match + */ + + if( vg_ui.textbox.cursor_pos == 0 ) return; + if( vg_ui.textbox.cursor_pos != vg_ui.textbox.cursor_user ) return; + if( vg_console.input[ vg_ui.textbox.cursor_pos ] != '\0' ) return; + + if( (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == ' ') || + (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == '\t') ) + return; + + char temp[128]; + const char *args[8]; + + int token_count = vg_console_tokenize( vg_console.input, temp, args ); + if( !token_count ) return; + vg_console.suggestion_pastepos = args[token_count-1]-temp; + + /* Score all our commands and cvars */ + if( token_count == 1 ){ + for( int i=0; iname, args[0], 1 ); + } + + for( int i=0; iname, args[0], 1 ); + } + } + else{ + vg_cmd *cmd = vg_console_match_cmd( args[0] ); + vg_var *var = vg_console_match_var( args[0] ); + + if( cmd ) + if( cmd->poll_suggest ) + cmd->poll_suggest( token_count-1, &args[1] ); + } + + /* some post processing */ + for( int i=0; i= vg_console.suggestion_count ) + vg_console.suggestion_select = -1; + + _console_fetch_suggestion(); + } +} + +static void console_suggest_prev(void) +{ + if( vg_console.suggestion_count ){ + _console_suggest_store_normal(); + + vg_console.suggestion_select --; + + if( vg_console.suggestion_select < -1 ) + vg_console.suggestion_select = vg_console.suggestion_count-1; + + _console_fetch_suggestion(); + } +} + +static void _vg_console_on_update( char *buf, u32 len ) +{ + if( buf == vg_console.input ){ + console_update_suggestions(); + } +} + +static void console_history_get( char* buf, int entry_num ) +{ + if( !vg_console.history_count ) + return; + + int offset = VG_MIN( entry_num, vg_console.history_count -1 ), + pick = (vg_console.history_last - offset) % + vg_list_size( vg_console.history ); + strcpy( buf, vg_console.history[ pick ] ); +} + +static void _vg_console_on_up( char *buf, u32 len ) +{ + if( buf == vg_console.input ){ + vg_console.history_pos = + VG_MAX + ( + 0, + VG_MIN + ( + vg_console.history_pos+1, + VG_MIN + ( + vg_list_size( vg_console.history ), + vg_console.history_count - 1 + ) + ) + ); + + console_history_get( vg_console.input, vg_console.history_pos ); + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, + vg_list_size(vg_console.input)-1, 1); + } +} + +static void _vg_console_on_down( char *buf, u32 len ) +{ + if( buf == vg_console.input ){ + vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 ); + console_history_get( vg_console.input, vg_console.history_pos ); + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, + vg_list_size(vg_console.input)-1, 1 ); + } +} + +static void _vg_console_on_enter( char *buf, u32 len ) +{ + if( buf == vg_console.input ){ + if( !strlen( vg_console.input ) ) + return; + + vg_info( "%s\n", vg_console.input ); + + if( strcmp( vg_console.input, + vg_console.history[ vg_console.history_last ]) ) + { + vg_console.history_last = ( vg_console.history_last + 1) % + vg_list_size(vg_console.history ); + vg_console.history_count = + VG_MIN( vg_list_size( vg_console.history ), + vg_console.history_count + 1 ); + strcpy( vg_console.history[ vg_console.history_last ], + vg_console.input ); + } + + vg_console.history_pos = -1; + vg_execute_console_input( vg_console.input, 0 ); + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, -10000, 1 ); + + vg_console.input[0] = '\0'; + console_update_suggestions(); + } +} + +int vg_console_exec( int argc, const char *argv[] ) +{ + if( argc < 1 ) return 0; + + int silent=0; + if( argc == 2 ) silent=1; + + char path[256]; + strcpy( path, "cfg/" ); + strncat( path, argv[0], 250 ); + + FILE *fp = fopen( path, "r" ); + if( fp ){ + char line[256]; + + while( fgets( line, sizeof( line ), fp ) ){ + line[ strcspn( line, "\r\n#" ) ] = 0x00; + + if( line[0] != 0x00 ){ + vg_execute_console_input( line, silent ); + } + } + + fclose( fp ); + } + else{ + vg_error( "Could not open '%s'\n", path ); + } + + return 0; +} + + +void vg_console_init(void) +{ + vg_console_reg_cmd( "list", _vg_console_list, NULL ); + vg_console_reg_cmd( "exec", vg_console_exec, NULL ); + vg_console_reg_var( "cheats", &vg_console.cheats, k_var_dtype_i32, +#ifdef VG_DEVWINDOW + VG_VAR_PERSISTENT +#else + 0 +#endif + ); +} + +void vg_console_load_autos(void) +{ + vg_console_exec( 2, (const char *[]){ "auto.conf", "silent" } ); +} + +void vg_console_draw(void) +{ + if( !vg_console.enabled ) return; + + SDL_AtomicLock( &vg_log.print_sl ); + + int ptr = vg_log.log_line_current; + int const fh = vg_ui.font->line_height, log_lines = 32; + int console_lines = VG_MIN( log_lines, vg_log.log_line_count ); + + ui_rect rect_log = { 0, 0, vg.window_x, log_lines*fh }, + rect_input = { 0, log_lines*fh + 1, vg.window_x, fh*2 }, + rect_line = { 0, 0, vg.window_x, fh }; + + /* + * log + */ + u32 bg_colour = (ui_colour( k_ui_bg )&0x00ffffff)|0x9f000000; + + ui_fill( rect_log, bg_colour ); + rect_line[1] = rect_log[1]+rect_log[3]-fh; + + for( int i=0; ispacing*vg_console.suggestion_pastepos; + rect_suggest[1] += rect_input[3]; + rect_suggest[2] = vg_ui.font->spacing * vg_console.suggestion_maxlen; + rect_suggest[3] = vg_console.suggestion_count * fh; + + ui_fill( rect_suggest, bg_colour ); + + rect_suggest[3] = fh; + + for( int i=0; i vg_list_size(vg_console.vars) ) - vg_fatal_error( "Too many vars registered" ); - - vg_var *var = &vg_console.vars[ vg_console.var_count ++ ]; - var->name = alias; - var->data = ptr; - var->data_type = type; - var->flags = flags; + u32 flags ); - vg_info( "Console variable '%s' registered\n", alias ); -} - -static void vg_console_reg_cmd( const char *alias, int (*function)(int argc, const char *argv[]), - void (*poll_suggest)(int argc, const char *argv[]) ) -{ - if( vg_console.function_count > vg_list_size(vg_console.functions) ) - vg_fatal_error( "Too many functions registered" ); - - vg_cmd *cmd = &vg_console.functions[ vg_console.function_count ++ ]; - - cmd->function = function; - cmd->poll_suggest = poll_suggest; - cmd->name = alias; - - vg_info( "Console function '%s' registered\n", alias ); -} - -static int _vg_console_list( int argc, char const *argv[] ){ - for( int i=0; iname ); - } - - for( int i=0; idata_type], - cv->name ); - } - - return 0; -} - -int _test_break( int argc, const char *argv[] ){ - vg_fatal_error( "Test crash from main, after loading (console)" ); - return 0; -} - -int _vg_console_exec( int argc, const char *argv[] ){ - if( argc < 1 ) return 0; - - int silent=0; - if( argc == 2 ) silent=1; - - char path[256]; - strcpy( path, "cfg/" ); - strncat( path, argv[0], 250 ); - - FILE *fp = fopen( path, "r" ); - if( fp ){ - char line[256]; - - while( fgets( line, sizeof( line ), fp ) ){ - line[ strcspn( line, "\r\n#" ) ] = 0x00; - - if( line[0] != 0x00 ){ - vg_execute_console_input( line, silent ); - } - } - - fclose( fp ); - } - else{ - vg_error( "Could not open '%s'\n", path ); - } - - return 0; -} - -static void _vg_console_init(void){ - vg_console_reg_cmd( "list", _vg_console_list, NULL ); - vg_console_reg_cmd( "crash", _test_break, NULL ); - vg_console_reg_cmd( "exec", _vg_console_exec, NULL ); - vg_console_reg_var( "cheats", &vg_console.cheats, k_var_dtype_i32, -#ifdef VG_DEVWINDOW - VG_VAR_PERSISTENT -#else - 0 -#endif - ); -} - -static void vg_console_load_autos(void){ - _vg_console_exec( 2, (const char *[]){ "auto.conf", "silent" } ); -} - -static void _vg_console_write_persistent(void){ - FILE *fp = fopen( "cfg/auto.conf", "w" ); - - if( !fp ){ - vg_error( "Cannot open cfg/auto.conf\n" ); - return; - } - - for( int i=0; iflags & VG_VAR_PERSISTENT ){ - if( cv->data_type == k_var_dtype_i32 ){ - fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) ); - } - else if( cv->data_type == k_var_dtype_u32 ){ - fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) ); - } - else if( cv->data_type == k_var_dtype_f32 ){ - fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) ); - } - else if( cv->data_type == k_var_dtype_str ){ - vg_str *str = cv->data; - if( str->buffer && (str->i > 0) ) - fprintf( fp, "%s %s\n", cv->name, str->buffer ); - } - } - } - - fclose( fp ); -} - -static void _vg_console_free(void) -{ - _vg_console_write_persistent(); -} - -/* - * splits src into tokens and fills out args as pointers to those tokens - * returns number of tokens - * dst must be as long as src - */ -static int vg_console_tokenize( const char *src, char *dst, - const char *args[8] ) -{ - int arg_count = 0, - in_token = 0; - - for( int i=0;; i ++ ){ - if( src[i] ){ - if( src[i] == ' ' || src[i] == '\t' ){ - if( in_token ) - dst[i] = '\0'; - - in_token = 0; - - if( arg_count == 8 ) - break; - } - else{ - dst[i] = src[i]; - - if( !in_token ){ - args[ arg_count ++ ] = &dst[i]; - in_token = 1; - } - } - } - else{ - dst[i] = '\0'; - break; - } - } - - return arg_count; -} - -static vg_var *vg_console_match_var( const char *kw ) -{ - for( int i=0; iname, kw ) ){ - return cv; - } - } - - return NULL; -} - -static vg_cmd *vg_console_match_cmd( const char *kw ) -{ - for( int i=0; iname, kw ) ){ - return cmd; - } - } - - return NULL; -} - -static void vg_execute_console_input( const char *cmd, bool silent ){ - char temp[512]; - char const *args[8]; - int arg_count = vg_console_tokenize( cmd, temp, args ); - - if( arg_count == 0 ) - return; - - vg_var *cv = vg_console_match_var( args[0] ); - vg_cmd *fn = vg_console_match_cmd( args[0] ); - - assert( !(cv && fn) ); - - if( cv ){ - /* Cvar Matched, try get value */ - if( arg_count >= 2 ){ - if( cv->flags & VG_VAR_CHEAT ){ - if( !vg_console.cheats && !silent ){ - vg_error( "variable is cheat protected\n" ); - return; - } - } - - if( (cv->data_type == k_var_dtype_u32) || - (cv->data_type == k_var_dtype_i32) ){ - int *ptr = cv->data; - *ptr = atoi( args[1] ); - } - else if( cv->data_type == k_var_dtype_f32 ){ - float *ptr = cv->data; - *ptr = atof( args[1] ); - } - else if( cv->data_type == k_var_dtype_str ){ - vg_str *str = cv->data; - vg_strfree( str ); - vg_strnull( str, NULL, -1 ); - - for( int i=1; idata_type == k_var_dtype_i32 ) - vg_info( "= %d\n", *((int *)cv->data) ); - else if( cv->data_type == k_var_dtype_u32 ) - vg_info( "= %u\n", *((u32 *)cv->data) ); - else if( cv->data_type == k_var_dtype_f32 ) - vg_info( "= %.4f\n", *((float *)cv->data) ); - else if( cv->data_type == k_var_dtype_str ){ - vg_str *str = cv->data; - vg_info( "= '%s'\n", str->buffer ); - } - } - - return; - } - else if( fn ){ - fn->function( arg_count-1, args+1 ); - return; - } - - if( !silent ) - vg_error( "No command/var named '%s'. Use 'list' to view all\n",args[0]); -} - -u32 str_lev_distance( const char *s1, const char *s2 ){ - u32 m = strlen( s1 ), - n = strlen( s2 ); - - if( m==0 ) return n; - if( n==0 ) return m; - - assert( n+1 <= 256 ); - - u32 costs[ 256 ]; - - for( u32 k=0; k<=n; k++ ) - costs[k] = k; - - u32 i = 0; - for( u32 i=0; i=0; j -- ) - if( score > vg_console.suggestions[j].lev_score ) - best_pos = j; - - /* insert if good score */ - if( best_pos < vg_list_size( vg_console.suggestions ) ){ - int start = VG_MIN( vg_console.suggestion_count, - vg_list_size( vg_console.suggestions )-1 ); - for( int j=start; j>best_pos; j -- ) - vg_console.suggestions[j] = vg_console.suggestions[j-1]; - - vg_console.suggestions[ best_pos ].str = str; - vg_console.suggestions[ best_pos ].len = strlen( str ); - vg_console.suggestions[ best_pos ].lev_score = score; - - if( vg_console.suggestion_count < - vg_list_size( vg_console.suggestions ) ) - vg_console.suggestion_count ++; - } -} - -static void console_update_suggestions(void) -{ - if( vg_ui.focused_control_type != k_ui_control_textbox || - vg_ui.textbuf != vg_console.input ) - return; - - vg_console.suggestion_count = 0; - vg_console.suggestion_select = -1; - vg_console.suggestion_maxlen = 0; - - /* - * - must be typing something - * - must be at the end - * - prev char must not be a whitespace - * - cursors should match - */ - - if( vg_ui.textbox.cursor_pos == 0 ) return; - if( vg_ui.textbox.cursor_pos != vg_ui.textbox.cursor_user ) return; - if( vg_console.input[ vg_ui.textbox.cursor_pos ] != '\0' ) return; - - if( (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == ' ') || - (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == '\t') ) - return; - - char temp[128]; - const char *args[8]; - - int token_count = vg_console_tokenize( vg_console.input, temp, args ); - if( !token_count ) return; - vg_console.suggestion_pastepos = args[token_count-1]-temp; - - /* Score all our commands and cvars */ - if( token_count == 1 ){ - for( int i=0; iname, args[0], 1 ); - } - - for( int i=0; iname, args[0], 1 ); - } - } - else{ - vg_cmd *cmd = vg_console_match_cmd( args[0] ); - vg_var *var = vg_console_match_var( args[0] ); - - assert( !( cmd && var ) ); - - if( cmd ) - if( cmd->poll_suggest ) - cmd->poll_suggest( token_count-1, &args[1] ); - } - - /* some post processing */ - for( int i=0; i= vg_console.suggestion_count ) - vg_console.suggestion_select = -1; - - _console_fetch_suggestion(); - } -} - -static void _console_suggest_prev(void) -{ - if( vg_console.suggestion_count ){ - _console_suggest_store_normal(); - - vg_console.suggestion_select --; - - if( vg_console.suggestion_select < -1 ) - vg_console.suggestion_select = vg_console.suggestion_count-1; - - _console_fetch_suggestion(); - } -} - -static void _vg_console_on_update( char *buf, u32 len ) -{ - if( buf == vg_console.input ){ - console_update_suggestions(); - } -} - -static void console_history_get( char* buf, int entry_num ) -{ - if( !vg_console.history_count ) - return; - - int offset = VG_MIN( entry_num, vg_console.history_count -1 ), - pick = (vg_console.history_last - offset) % - vg_list_size( vg_console.history ); - strcpy( buf, vg_console.history[ pick ] ); -} - -static void _vg_console_on_up( char *buf, u32 len ) -{ - if( buf == vg_console.input ){ - vg_console.history_pos = - VG_MAX - ( - 0, - VG_MIN - ( - vg_console.history_pos+1, - VG_MIN - ( - vg_list_size( vg_console.history ), - vg_console.history_count - 1 - ) - ) - ); - - console_history_get( vg_console.input, vg_console.history_pos ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, - vg_list_size(vg_console.input)-1, 1); - } -} - -static void _vg_console_on_down( char *buf, u32 len ) -{ - if( buf == vg_console.input ){ - vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 ); - console_history_get( vg_console.input, vg_console.history_pos ); - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, - vg_list_size(vg_console.input)-1, 1 ); - } -} - -static void _vg_console_on_enter( char *buf, u32 len ) -{ - if( buf == vg_console.input ){ - if( !strlen( vg_console.input ) ) - return; - - vg_info( "%s\n", vg_console.input ); - - if( strcmp( vg_console.input, - vg_console.history[ vg_console.history_last ]) ) - { - vg_console.history_last = ( vg_console.history_last + 1) % - vg_list_size(vg_console.history ); - vg_console.history_count = - VG_MIN( vg_list_size( vg_console.history ), - vg_console.history_count + 1 ); - strcpy( vg_console.history[ vg_console.history_last ], - vg_console.input ); - } - - vg_console.history_pos = -1; - vg_execute_console_input( vg_console.input, 0 ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, -10000, 1 ); - - vg_console.input[0] = '\0'; - console_update_suggestions(); - } -} - -static void _vg_console_draw(void) -{ - if( !vg_console.enabled ) return; - - SDL_AtomicLock( &log_print_sl ); - - int ptr = vg_log.log_line_current; - int const fh = vg_ui.font->line_height, log_lines = 32; - int console_lines = VG_MIN( log_lines, vg_log.log_line_count ); - - ui_rect rect_log = { 0, 0, vg.window_x, log_lines*fh }, - rect_input = { 0, log_lines*fh + 1, vg.window_x, fh*2 }, - rect_line = { 0, 0, vg.window_x, fh }; - - /* - * log - */ - u32 bg_colour = (ui_colour( k_ui_bg )&0x00ffffff)|0x9f000000; - - ui_fill( rect_log, bg_colour ); - rect_line[1] = rect_log[1]+rect_log[3]-fh; - - for( int i=0; ispacing*vg_console.suggestion_pastepos; - rect_suggest[1] += rect_input[3]; - rect_suggest[2] = vg_ui.font->spacing * vg_console.suggestion_maxlen; - rect_suggest[3] = vg_console.suggestion_count * fh; - - ui_fill( rect_suggest, bg_colour ); - - rect_suggest[3] = fh; - - for( int i=0; istereo + */ +int +stb_vorbis_get_samples_float_interleaved_stereo( stb_vorbis *f, float *buffer, + int len ) +{ + int n = 0, c = 1; + if( f->channels < 2 ) c = 0; + + while( n < len ) { + int k = f->channel_buffer_end - f->channel_buffer_start; + + if( n+k >= len ) + k = len - n; + + for( int j=0; j < k; ++j ) { + *buffer++ = f->channel_buffers[ 0 ][f->channel_buffer_start+j]; + *buffer++ = f->channel_buffers[ c ][f->channel_buffer_start+j]; + } + + n += k; + f->channel_buffer_start += k; + + if( n == len ) + break; + + if( !stb_vorbis_get_frame_float( f, NULL, NULL )) + break; + } + + return n; +} + +/* + * ........ more wrecked code sorry! + */ +int +stb_vorbis_get_samples_i16_downmixed( stb_vorbis *f, i16 *buffer, int len ) +{ + int n = 0, c = 1; + if( f->channels < 2 ) c = 0; + + while( n < len ) { + int k = f->channel_buffer_end - f->channel_buffer_start; + + if( n+k >= len ) + k = len - n; + + for( int j=0; j < k; ++j ) { + float sl = f->channel_buffers[ 0 ][f->channel_buffer_start+j], + sr = f->channel_buffers[ c ][f->channel_buffer_start+j]; + + *buffer++ = vg_clampf( 0.5f*(sl+sr), -1.0f, 1.0f ) * 32767.0f; + //*buffer++ = vg_clampf( sr, -1.0f, 1.0f ) * 32767.0f; + } + + n += k; + f->channel_buffer_start += k; + + if( n == len ) + break; + + if( !stb_vorbis_get_frame_float( f, NULL, NULL )) + break; + } + + return n; +} diff --git a/vg_engine.c b/vg_engine.c new file mode 100644 index 0000000..0d8c015 --- /dev/null +++ b/vg_engine.c @@ -0,0 +1,1192 @@ +#include "vg_engine.h" +#include "vg_async.h" + +struct vg_engine vg = { + .time_rate = 1.0, + .time_fixed_delta = VG_TIMESTEP_FIXED +}; + +#include + +enum engine_status _vg_engine_status(void) +{ + SDL_AtomicLock( &vg.sl_status ); + enum engine_status status = vg.engine_status; + SDL_AtomicUnlock( &vg.sl_status ); + + return status; +} + +enum vg_thread_purpose vg_thread_purpose(void) +{ + SDL_AtomicLock( &vg.sl_status ); + + if( vg.thread_id_main == SDL_GetThreadID(NULL) ){ + SDL_AtomicUnlock( &vg.sl_status ); + return k_thread_purpose_main; + } + else{ + SDL_AtomicUnlock( &vg.sl_status ); + return k_thread_purpose_loader; + } +} + +static void vg_assert_thread( enum vg_thread_purpose required ) +{ + enum vg_thread_purpose purpose = vg_thread_purpose(); + + if( purpose != required ){ + vg_fatal_error( "thread_purpose must be %u not %u\n", required, purpose ); + } +} + +static void _vg_opengl_sync_init(void) +{ + vg.sem_loader = SDL_CreateSemaphore(1); +} + +#include "vg_console.h" +#include "vg_profiler.h" +#ifndef VG_NO_AUDIO + #include "vg_audio.h" +#endif +#include "vg_shader.h" +#include "vg_tex.h" +#include "vg_input.h" +#include "vg_imgui.h" +#include "vg_lines.h" +#include "vg_rigidbody_view.h" +#include "vg_loader.h" +#include "vg_opt.h" + +/* Diagnostic */ +static struct vg_profile vg_prof_update = {.name="update()"}, + vg_prof_render = {.name="render()"}, + vg_prof_swap = {.name="swap"}; + +void vg_checkgl( const char *src_info ) +{ + int fail = 0; + + GLenum err; + while( (err = glGetError()) != GL_NO_ERROR ){ + vg_error( "(%s) OpenGL Error: #%d\n", src_info, err ); + fail = 1; + } + + if( fail ) + vg_fatal_error( "OpenGL Error" ); +} + +static void async_vg_bake_shaders( void *payload, u32 size ) +{ + vg_shaders_compile(); +} + +void vg_bake_shaders(void) +{ + vg_console_reg_cmd( "reload_shaders", vg_shaders_live_recompile, NULL ); + vg_async_call( async_vg_bake_shaders, NULL, 0 ); +} + +void async_internal_complete( void *payload, u32 size ) +{ + vg_success( "Internal async setup complete\n" ); + SDL_AtomicLock( &vg.sl_status ); + + if( vg.engine_status == k_engine_status_crashed ){ + SDL_AtomicUnlock( &vg.sl_status ); + return; + } + else{ + vg.engine_status = k_engine_status_running; + } + + SDL_AtomicUnlock( &vg.sl_status ); +} + +static void _vg_load_full( void *data ) +{ + vg_preload(); + vg_tex2d_replace_with_error_async( &vg.tex_missing ); + vg_ui.tex_bg = vg.tex_missing; + + /* internal */ + vg_loader_step( vg_input_init, vg_input_free ); + vg_loader_step( vg_lines_init, NULL ); + vg_loader_step( vg_rb_view_init, NULL ); +#ifndef VG_NO_AUDIO + vg_loader_step( vg_audio_init, vg_audio_free ); +#endif + vg_loader_step( vg_profiler_init, NULL ); + + /* client */ + vg_load(); + + vg_async_call( async_internal_complete, NULL, 0 ); + + vg_success( "Client loaded in %fs\n", vg.time_real ); +} + +static void _vg_process_events(void) +{ + v2_zero( vg.mouse_wheel ); + v2_zero( vg.mouse_delta ); + + /* Update input */ + vg_process_inputs(); + + /* SDL event loop */ + SDL_Event event; + while( SDL_PollEvent( &event ) ){ + if( event.type == SDL_KEYDOWN ){ + if( vg_console.enabled && + (vg_ui.focused_control_type != k_ui_control_modal) ){ + if( event.key.keysym.sym == SDLK_ESCAPE || + event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ + vg_console.enabled = 0; + ui_defocus_all(); + } + else if( (event.key.keysym.mod & KMOD_CTRL) && + event.key.keysym.sym == SDLK_n ){ + console_suggest_next(); + } + else if( (event.key.keysym.mod & KMOD_CTRL ) && + event.key.keysym.sym == SDLK_p ){ + console_suggest_prev(); + } + else{ + ui_proc_key( event.key.keysym ); + } + } + else{ + if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ + vg_console.enabled = 1; + } + else { + ui_proc_key( event.key.keysym ); + } + } + } + else if( event.type == SDL_MOUSEWHEEL ){ + vg.mouse_wheel[0] += event.wheel.preciseX; + vg.mouse_wheel[1] += event.wheel.preciseY; + } + else if( event.type == SDL_CONTROLLERDEVICEADDED || + event.type == SDL_CONTROLLERDEVICEREMOVED ) + { + vg_input_device_event( &event ); + } + else if( event.type == SDL_CONTROLLERAXISMOTION || + event.type == SDL_CONTROLLERBUTTONDOWN || + event.type == SDL_CONTROLLERBUTTONUP ) + { + vg_input_controller_event( &event ); + } + else if( event.type == SDL_MOUSEMOTION ){ + vg.mouse_delta[0] += event.motion.xrel; + vg.mouse_delta[1] += event.motion.yrel; + } + else if( event.type == SDL_WINDOWEVENT ){ + if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ){ + int w, h; + SDL_GL_GetDrawableSize( vg.window, &w, &h ); + + if( !w || !h ){ + vg_warn( "Got a invalid framebuffer size: " + "%dx%d... ignoring\n", w, h ); + } + else{ + vg.window_x = w; + vg.window_y = h; + + vg_framebuffer_resize(w,h); + } + } + else if( event.window.event == SDL_WINDOWEVENT_CLOSE ){ + vg.window_should_close = 1; + } + } + else if( event.type == SDL_TEXTINPUT ){ + ui_proc_utf8( event.text.text ); + } + } + + SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] ); +} + +static void _vg_gameloop_update(void) +{ + vg_profile_begin( &vg_prof_update ); + + vg.engine_stage = k_engine_stage_update; + vg_pre_update(); + + /* Fixed update loop */ + vg.engine_stage = k_engine_stage_update_fixed; + + vg.fixed_iterations = 0; + vg_lines.enabled = vg_lines.render; + vg.time_fixed_accumulator += vg.time_delta; + + while( vg.time_fixed_accumulator >= vg.time_fixed_delta ){ + vg_fixed_update(); + vg_lines.enabled = 0; + vg.time_fixed_accumulator -= vg.time_fixed_delta; + + vg.fixed_iterations ++; + if( vg.fixed_iterations == 8 ){ + break; + } + } + vg_lines.enabled = vg_lines.render; + vg.time_fixed_extrapolate = vg.time_fixed_accumulator / vg.time_fixed_delta; + + vg.engine_stage = k_engine_stage_update; + vg_post_update(); + vg_profile_end( &vg_prof_update ); +} + +static void vg_settings_gui(void); +static void _vg_gameloop_render(void) +{ + vg_profile_begin( &vg_prof_render ); + + /* render */ + vg.engine_stage = k_engine_stage_rendering; + vg_render(); + + vg_profile_end( &vg_prof_render ); + + /* ui */ + vg.engine_stage = k_engine_stage_ui; + { + ui_prerender(); + if( vg_console.enabled ){ + vg_ui.ignore_input_frames = 10; + vg_gui(); + vg_ui.ignore_input_frames = 0; + vg_ui.wants_mouse = 1; + vg_console_draw(); + } + else vg_gui(); + + if( vg.settings_open ) + vg_settings_gui(); + + /* vg tools */ +#ifndef VG_NO_AUDIO + audio_debug_ui( vg.pv ); +#endif + + /* profiling */ + if( vg_profiler ){ + int frame_target = vg.display_refresh_rate; + if( vg.fps_limit > 0 ) frame_target = vg.fps_limit; + vg_profile_drawn( + (struct vg_profile *[]){ + &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3, + (1.0f/(float)frame_target)*1000.0f, + (ui_rect){ 4, 4, 250, 0 }, 0, 0 + ); + char perf[256]; + + snprintf( perf, 255, + "x: %d y: %d\n" + "refresh: %d (%.1fms)\n" + "samples: %d\n" + "iterations: %d (acc: %.3fms%%)\n" + "time: real(%.2f) delta(%.2f) rate(%.2f)\n" + " extrap(%.2f) frame(%.2f) spin( "PRINTF_U64" )\n", + vg.window_x, vg.window_y, + frame_target, (1.0f/(float)frame_target)*1000.0f, + vg.samples, + vg.fixed_iterations, + (vg.time_fixed_accumulator/VG_TIMESTEP_FIXED)*100.0f, + vg.time_real, vg.time_delta, vg.time_rate, + vg.time_fixed_extrapolate, vg.time_frame_delta, + vg.time_spinning ); + + ui_text( (ui_rect){258,4,900,900},perf,1,0,k_ui_align_left); + } + ui_postrender(); + } +} + +static void vg_changevsync(void){ + if( vg.vsync && (vg.vsync_feature != k_vsync_feature_error) ){ + /* turn on vsync if not enabled */ + + enum vsync_feature requested = k_vsync_feature_enabled; + if( vg.vsync < 0 ) requested = k_vsync_feature_enabled_adaptive; + + if( vg.vsync_feature != requested ){ + vg_info( "Setting swap interval\n" ); + + int swap_interval = 1; + if( requested == k_vsync_feature_enabled_adaptive ) + swap_interval = -1; + + if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ){ + if( requested == k_vsync_feature_enabled ){ + vg_error( "Vsync is not supported by your system\n" ); + vg_warn( "You may be overriding it in your" + " graphics control panel.\n" ); + } + else{ + vg_error( "Adaptive Vsync is not supported by your system\n" ); + } + + vg.vsync_feature = k_vsync_feature_error; + vg.vsync = 0; + /* TODO: Make popup to notify user that this happened */ + } + else{ + vg_success( "Vsync enabled (%d)\n", requested ); + vg.vsync_feature = requested; + } + } + } + else { + if( vg.vsync_feature != k_vsync_feature_disabled ){ + SDL_GL_SetSwapInterval( 0 ); + vg.vsync_feature = k_vsync_feature_disabled; + } + } +} + +static int vg_framefilter( double dt ){ + if( vg.fps_limit < 24 ) vg.fps_limit = 24; + if( vg.fps_limit > 300 ) vg.fps_limit = 300; + + double min_frametime = 1.0/(double)vg.fps_limit; + if( vg.time_frame_delta < min_frametime ){ + /* TODO: we can use high res nanosleep on Linux here */ + double sleep_ms = (min_frametime-vg.time_frame_delta) * 1000.0; + u32 ms = (u32)floor( sleep_ms ); + + if( ms ){ + if( !vg_loader_availible() ) + SDL_Delay(1); + else + SDL_Delay(ms); + } + else{ + vg.time_spinning ++; + } + + return 1; + } + + return 0; +} + +static int _vg_crashscreen(void) +{ +#if 0 + if( vg_getkey( SDLK_ESCAPE ) ) + return 1; +#endif + + glBindFramebuffer( GL_FRAMEBUFFER, 0 ); + glEnable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA); + glBlendEquation(GL_FUNC_ADD); + + glClearColor( 0.15f + sinf(vg.time_real)*0.1f, 0.0f, 0.0f,1.0f ); + glClear( GL_COLOR_BUFFER_BIT ); + glViewport( 0,0, vg.window_x, vg.window_y ); + +#if 0 + _vg_render_log(); +#endif + + return 0; +} + +static void _vg_gameloop(void){ + //vg.time_fixed_accumulator = 0.75f * (1.0f/60.0f); + + vg.time_hp = SDL_GetPerformanceCounter(); + vg.time_hp_last = vg.time_hp; + + int post_start = 0; + while(1){ + vg.time_hp = SDL_GetPerformanceCounter(); + u64 udt = vg.time_hp - vg.time_hp_last; + vg.time_hp_last = vg.time_hp; + + double dt = (double)udt / (double)SDL_GetPerformanceFrequency(); + + vg.time_frame_delta += dt; + vg_run_async_checked(); + + if( vg_framefilter( dt ) ) + continue; + + vg_changevsync(); + + enum engine_status status = _vg_engine_status(); + if( status == k_engine_status_running ) + vg_profile_begin( &vg_prof_swap ); + + SDL_GL_SwapWindow( vg.window ); + + if( status == k_engine_status_running ) + vg_profile_end( &vg_prof_swap ); + + vg.time_real += vg.time_frame_delta; + vg.time_delta = vg.time_frame_delta * vg.time_rate; + vg.time += vg.time_delta; + + _vg_process_events(); + + if( vg.window_should_close ) + break; + + if( status == k_engine_status_crashed ){ + if( _vg_crashscreen() ) + break; + } + else{ + if( status == k_engine_status_running ){ + _vg_gameloop_update(); + _vg_gameloop_render(); + } + else{ + vg_loader_render(); + } + } + + if( vg.loader_ring > 0.01f ){ + vg_loader_render_ring( vg.loader_ring ); + vg.loader_ring -= vg.time_frame_delta * 0.5f; + } + + vg.time_frame_delta = 0.0; + vg.time_spinning = 0; + } +} + +static void _vg_process_launch_opts_internal( int argc, char *argv[] ) +{ + char *arg; + while( vg_argp( argc, argv ) ){ + if( (arg = vg_opt_arg( 'w' )) ){ + vg.window_x = atoi( arg ); + } + + if( (arg = vg_opt_arg( 'h' )) ){ + vg.window_y = atoi( arg ); + } + + if( (arg = vg_long_opt_arg( "samples" )) ){ + vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) ); + } + + if( vg_long_opt( "use-libc-malloc" ) ){ + vg_mem.use_libc_malloc = 1; + } + + if( vg_long_opt( "high-performance" ) ){ + vg.quality_profile = k_quality_profile_low; + } + + vg_launch_opt(); + } +} + +static void _vg_init_window( const char *window_name ) +{ + vg_info( "SDL_INIT\n" ); + + if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){ + vg_error( "SDL_Init failed: %s\n", SDL_GetError() ); + exit(0); + } + +#ifndef VG_NO_AUDIO + SDL_InitSubSystem( SDL_INIT_AUDIO ); +#endif + SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER ); + + char *exe_basepath = SDL_GetBasePath(); + u32 len = vg_align8( strlen(exe_basepath)+1 ); + char *dest = vg_linear_alloc( vg_mem.rtmemory, len ); + strcpy( dest, exe_basepath ); + SDL_free( exe_basepath ); + vg.base_path = dest; + + vg_info( "Basepath: %s\n", vg.base_path ); + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, + SDL_GL_CONTEXT_PROFILE_CORE ); + + SDL_GL_SetAttribute( SDL_GL_CONTEXT_RELEASE_BEHAVIOR, + SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH ); + + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0 ); + + /* + * Get monitor information + */ + vg_info( "Getting display count\n" ); + int display_count = 0, + display_index = 0, + mode_index = 0; + + SDL_DisplayMode video_mode; + if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ){ + vg_error( "SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError() ); + SDL_Quit(); + exit(0); + } + + vg.display_refresh_rate = video_mode.refresh_rate; + vg.window_x = video_mode.w; + vg.window_y = video_mode.h; + + if( vg.screen_mode == 2 ){ + vg.window_x = 1280; + vg.window_y = 720; + } + +#ifndef _WIN32 + SDL_SetHint( "SDL_VIDEO_X11_XINERAMA", "1" ); + SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "0" ); + SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "0" ); +#endif + + u32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_GRABBED | + SDL_WINDOW_RESIZABLE; + + if( vg.screen_mode == 1 ) + flags |= SDL_WINDOW_FULLSCREEN; + else if( vg.screen_mode == 0 ) + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + + vg_info( "CreateWindow( %d %d %u )\n", vg.window_x, vg.window_y, flags ); + + if((vg.window = SDL_CreateWindow( window_name, 0, 0, + vg.window_x, vg.window_y, flags ))){ + if( vg.screen_mode == 2 ) + SDL_SetWindowPosition( vg.window, video_mode.w-vg.window_x, 0 ); + } + else{ + vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() ); + exit(0); + } + + SDL_RaiseWindow( vg.window ); + SDL_SetWindowMinimumSize( vg.window, 1280, 720 ); + SDL_SetWindowMaximumSize( vg.window, 4096, 4096 ); + + vg_info( "CreateContext\n" ); + + /* ????? */ + if( SDL_IsTextInputActive() ) SDL_StopTextInput(); + + /* + * OpenGL loading + */ + if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )){ + SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y ); + vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y ); + } + else{ + vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); + SDL_Quit(); + exit(0); + } + + if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) { + vg_error( "Glad Failed to initialize\n" ); + SDL_GL_DeleteContext( vg.gl_context ); + SDL_Quit(); + exit(0); + } + + const unsigned char* glver = glGetString( GL_VERSION ); + vg_success( "Load setup complete, OpenGL version: %s\n", glver ); + + SDL_GL_SetSwapInterval(0); /* disable vsync while loading */ + + SDL_DisplayMode dispmode; + if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ){ + if( dispmode.refresh_rate ){ + vg.display_refresh_rate = dispmode.refresh_rate; + } + } + + if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ){ + vg.display_refresh_rate = 60; + } + + vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate ); + if( !vg.fps_limit) vg.fps_limit = vg.display_refresh_rate; +} + +static void _vg_terminate(void) +{ + /* Shutdown */ + vg_console_write_persistent(); + + SDL_AtomicLock( &vg.sl_status ); + vg.engine_status = k_engine_status_none; + SDL_AtomicUnlock( &vg.sl_status ); + + vg_loader_free(); + + vg_success( "If you see this it means everything went.. \"well\".....\n" ); + + SDL_GL_DeleteContext( vg.gl_context ); + SDL_Quit(); + exit(0); +} + +static int cmd_vg_settings_toggle( int argc, const char *argv[] ); +void vg_enter( int argc, char *argv[], const char *window_name ) +{ + vg_rand_seed( &vg.rand, 461 ); + _vg_process_launch_opts_internal( argc, argv ); + + /* Systems init */ + vg_alloc_quota(); + vg_console_init(); + + vg_console_reg_var( "vg_fps_limit", &vg.fps_limit, + k_var_dtype_i32, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_vsync", &vg.vsync, + k_var_dtype_i32, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_quality", &vg.quality_profile, + k_var_dtype_i32, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_screen_mode", &vg.screen_mode, + k_var_dtype_i32, VG_VAR_PERSISTENT ); + vg_audio_register(); + vg_console_load_autos(); + + vg_console_reg_cmd( "vg_settings", cmd_vg_settings_toggle, NULL ); + _vg_init_window( window_name ); + + vg_async_init(); + SDL_SetRelativeMouseMode(1); + + vg.thread_id_main = SDL_GetThreadID(NULL); + + /* Opengl-required systems */ + vg_ui_init(); + vg_loader_init(); + + vg.engine_status = k_engine_status_load_internal; + + _vg_opengl_sync_init(); + vg_loader_start( _vg_load_full, NULL ); + _vg_gameloop(); + _vg_terminate(); +} + +void vg_fatal_error( const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + _vg_logx_va( stderr, NULL, "fatal", KRED, fmt, args ); + va_end( args ); + + vg_print_backtrace(); + + SDL_AtomicLock( &vg.sl_status ); + vg.engine_status = k_engine_status_crashed; + SDL_AtomicUnlock( &vg.sl_status ); + + if( vg_thread_purpose() == k_thread_purpose_loader ) + { + longjmp( vg.env_loader_exit, 1 ); + } + else + { + vg_error( "There is no jump to the error runner thing yet! bai bai\n" ); + _vg_terminate(); + } +} + +/* + * settings menu + * --------------------------------------------------------------------------- + */ + +#ifdef VG_GAME_SETTINGS +extern void vg_game_settings_gui( ui_rect panel ) ; +extern void vg_game_settings_init(void); +#endif + +struct ui_enum_opt vg_settings_vsync_enum[] = { + { 0, "None" }, + { 1, "On" }, + {-1, "Adaptive" }, +}; + +struct ui_enum_opt vg_settings_quality_enum[] = { + { 0, "High Quality" }, + { 1, "Faster" }, + { 2, "Absolute Minimum" }, +}; + +struct ui_enum_opt vg_settings_screen_mode_enum[] = { + { 0, "Fullscreen (desktop)" }, + { 1, "Fullscreen (native)" }, + { 2, "Floating Window" } +}; + +struct ui_enum_opt vg_settings_dsp_enum[] = { + { 1, "Enabled" }, + { 0, "Disabled" }, +}; + +struct { + struct vg_setting_ranged_i32 fps_limit; + struct vg_setting_enum vsync, quality, screenmode, audio_devices, dsp; + i32 temp_audio_choice; +} +static vg_settings = { + .fps_limit = { .label = "Fps Limit", + .min=24, .max=300, .actual_value = &vg.fps_limit }, + .vsync = { .label = "Vsync", + .actual_value = &vg.vsync, + .options = vg_settings_vsync_enum, .option_count = 3 }, + .quality = { .label = "Graphic Quality", + .actual_value = &vg.quality_profile, + .options = vg_settings_quality_enum, .option_count = 3 }, + .screenmode = { .label = "Type", + .actual_value = &vg.screen_mode, + .options = vg_settings_screen_mode_enum, .option_count=3 }, + .audio_devices = { .label = "Audio Device", + .actual_value = &vg_settings.temp_audio_choice, + .options = NULL, .option_count = 0 }, + .dsp = { .label = "Audio effects (reverb etc.)", + .actual_value = &vg_audio.dsp_enabled, + .options = vg_settings_dsp_enum, .option_count=2 }, +}; + +static void vg_settings_ui_draw_diff( ui_rect orig ){ + ui_rect l,r; + ui_split( orig, k_ui_axis_v, -32, 0, l, r ); + ui_text( r, "*", 1, k_ui_align_middle_center, ui_colour(k_ui_blue) ); +} + +/* i32 settings + * ------------------------------------------------------------------------- */ + +static void vg_settings_ui_int( char *buf, u32 len ){ + for( u32 i=0, j=0; i= '0') && (buf[i] <= '9')) || (buf[i] == '\0') ) + buf[j ++] = buf[i]; + } +} + +struct ui_textbox_callbacks static vg_settings_ui_int_callbacks = { + .change = vg_settings_ui_int +}; + +static bool vg_settings_ranged_i32_valid( struct vg_setting_ranged_i32 *prop ){ + if( prop->new_value < prop->min ) return 0; + if( prop->new_value > prop->max ) return 0; + return 1; +} + +static bool vg_settings_ranged_i32_diff( struct vg_setting_ranged_i32 *prop ){ + if( prop->new_value != *prop->actual_value ) return 1; + else return 0; +} + +static bool vg_settings_ui_ranged_i32( struct vg_setting_ranged_i32 *prop, + ui_rect rect ){ + ui_rect orig; + rect_copy( rect, orig ); + + ui_textbox( rect, prop->label, prop->buf, sizeof(prop->buf), + 1, 0, &vg_settings_ui_int_callbacks ); + prop->new_value = atoi( prop->buf ); + + if( vg_settings_ranged_i32_diff( prop ) ) + vg_settings_ui_draw_diff( orig ); + + bool valid = vg_settings_ranged_i32_valid( prop ); + if( !valid ){ + ui_rect _null, line; + ui_split( orig, k_ui_axis_h, -1, 0, _null, line ); + line[1] += 3; + + ui_fill( line, ui_colour( k_ui_red ) ); + } + + return valid; +} + +void ui_settings_ranged_i32_init( struct vg_setting_ranged_i32 *prop ) +{ + vg_str tmp; + vg_strnull( &tmp, prop->buf, sizeof(prop->buf) ); + vg_strcati32( &tmp, *prop->actual_value ); + prop->new_value = *prop->actual_value; +} + +/* enum settings + * ------------------------------------------------------------------------- */ + +bool vg_settings_enum_diff( struct vg_setting_enum *prop ) +{ + if( prop->new_value != *prop->actual_value ) return 1; + else return 0; +} + +bool vg_settings_enum( struct vg_setting_enum *prop, ui_rect rect ) +{ + ui_rect orig; + rect_copy( rect, orig ); + + ui_enum( rect, prop->label, + prop->options, prop->option_count, &prop->new_value ); + + if( vg_settings_enum_diff( prop ) ) + vg_settings_ui_draw_diff( orig ); + + return 1; +} + +void ui_settings_enum_init( struct vg_setting_enum *prop ) +{ + prop->new_value = *prop->actual_value; +} + +/* .. */ + +void vg_settings_ui_header( ui_rect inout_panel, const char *name ) +{ + ui_rect rect; + ui_standard_widget( inout_panel, rect, 2 ); + ui_text( rect, name, 1, k_ui_align_middle_center, ui_colour(k_ui_fg+3) ); +} + + +bool vg_settings_apply_button( ui_rect inout_panel, bool validated ) +{ + ui_rect last_row; + ui_px height = (vg_ui.font->glyph_height + 18) * k_ui_scale; + ui_split( inout_panel, k_ui_axis_h, -height, k_ui_padding, + inout_panel, last_row ); + + const char *string = "Apply"; + if( validated ){ + if( ui_button( last_row, string ) == 1 ) + return 1; + } + else{ + ui_rect rect; + ui_standard_widget( last_row, rect, 1 ); + ui_fill( rect, ui_colour( k_ui_bg+1 ) ); + ui_outline( rect, -1, ui_colour( k_ui_red ), 0 ); + + ui_rect t = { 0,0, ui_text_line_width( string ), 14 }; + ui_rect_center( rect, t ); + ui_text( t, string, 1, k_ui_align_left, ui_colour(k_ui_fg+3) ); + } + + return 0; +} + +static void vg_settings_video_apply(void){ + if( vg_settings_enum_diff( &vg_settings.screenmode ) ){ + vg.screen_mode = vg_settings.screenmode.new_value; + + if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ){ + SDL_DisplayMode video_mode; + if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ){ + vg_error("SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError()); + } + else { + //vg.display_refresh_rate = video_mode.refresh_rate; + vg.window_x = video_mode.w; + vg.window_y = video_mode.h; + } + SDL_SetWindowSize( vg.window, vg.window_x, vg.window_y ); + } + + if( vg.screen_mode == 0 ) + SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN_DESKTOP ); + if( vg.screen_mode == 1 ) + SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN ); + if( vg.screen_mode == 2 ){ + SDL_SetWindowFullscreen( vg.window, 0 ); + SDL_SetWindowSize( vg.window, 1280, 720 ); + SDL_SetWindowPosition( vg.window, 16, 16 ); + SDL_SetWindowMinimumSize( vg.window, 1280, 720 ); + SDL_SetWindowMaximumSize( vg.window, 4096, 4096 ); + } + } + + vg.fps_limit = vg_settings.fps_limit.new_value; + vg.quality_profile = vg_settings.quality.new_value; + vg.vsync = vg_settings.vsync.new_value; +} + +static void vg_settings_video_gui( ui_rect panel ){ + bool validated = 1; + ui_rect rq; + ui_standard_widget( panel, rq, 1 ); + vg_settings_enum( &vg_settings.quality, rq ); + + /* FIXME */ +#if 0 + if( vg.vsync_feature == k_vsync_feature_error ){ + ui_info( panel, "There was an error activating vsync feature." ); + } +#endif + + /* frame timing */ + vg_settings_ui_header( panel, "Frame Timing" ); + ui_rect duo, d0,d1; + ui_standard_widget( panel, duo, 1 ); + ui_split_ratio( duo, k_ui_axis_v, 0.5f, 16, d0, d1 ); + + vg_settings_enum( &vg_settings.vsync, d0 ); + validated &= vg_settings_ui_ranged_i32( &vg_settings.fps_limit, d1 ); + + /* profiler */ + ui_standard_widget( panel, duo, 10 ); + int frame_target = vg.display_refresh_rate; + if( !vg.vsync ) frame_target = vg.fps_limit; + vg_profile_drawn( + (struct vg_profile *[]){ + &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3, + (1.0f/(f32)frame_target)*1500.0f, + duo, 1, 0 + ); + + ui_fill( (ui_rect){ duo[0], duo[1]+(duo[3]*2)/3, duo[2], 1 }, + ui_colour(k_ui_fg) ); + + /* window spec */ + vg_settings_ui_header( panel, "Window Specification" ); + + ui_standard_widget( panel, duo, 1 ); + vg_settings_enum( &vg_settings.screenmode, duo ); + + if( vg_settings_apply_button( panel, validated ) ) + vg_settings_video_apply(); +} + +static void vg_settings_audio_apply(void){ + if( vg_settings_enum_diff( &vg_settings.audio_devices ) ){ + if( vg_audio.sdl_output_device ){ + vg_info( "Closing audio device %d\n", vg_audio.sdl_output_device ); + SDL_CloseAudioDevice( vg_audio.sdl_output_device ); + } + + vg_strfree( &vg_audio.device_choice ); + + if( vg_settings.audio_devices.new_value == -1 ){ } + else if( vg_settings.audio_devices.new_value == -2 ){ + vg_fatal_error( "Programming error\n" ); + } + else { + struct ui_enum_opt *selected = NULL, *oi; + + for( int i=0; ivalue == vg_settings.audio_devices.new_value ){ + selected = oi; + break; + } + } + + vg_strnull( &vg_audio.device_choice, NULL, -1 ); + vg_strcat( &vg_audio.device_choice, oi->alias ); + } + + vg_audio_device_init(); + *vg_settings.audio_devices.actual_value = + vg_settings.audio_devices.new_value; + } + + audio_lock(); + if( vg_settings_enum_diff( &vg_settings.dsp ) ){ + *vg_settings.dsp.actual_value = + vg_settings.dsp.new_value; + } + + audio_unlock(); +} + +static void vg_settings_audio_gui( ui_rect panel ){ + ui_rect rq; + ui_standard_widget( panel, rq, 1 ); + vg_settings_enum( &vg_settings.audio_devices, rq ); + + ui_standard_widget( panel, rq, 1 ); + vg_settings_enum( &vg_settings.dsp, rq ); + + if( vg_settings_apply_button( panel, 1 ) ) + vg_settings_audio_apply(); +} + +void vg_settings_open(void) +{ + vg.settings_open = 1; + + ui_settings_ranged_i32_init( &vg_settings.fps_limit ); + ui_settings_enum_init( &vg_settings.vsync ); + ui_settings_enum_init( &vg_settings.quality ); + ui_settings_enum_init( &vg_settings.screenmode ); + + /* Create audio options */ + int count = SDL_GetNumAudioDevices( 0 ); + + struct ui_enum_opt *options = malloc( sizeof(struct ui_enum_opt)*(count+1) ); + vg_settings.audio_devices.options = options; + vg_settings.audio_devices.option_count = count+1; + + struct ui_enum_opt *o0 = &options[0]; + o0->alias = "OS Default"; + o0->value = -1; + + for( int i=0; ialias = malloc( len+1 ); + memcpy( (void *)oi->alias, device_name, len+1 ); + oi->value = i; + } + + if( vg_audio.device_choice.buffer ){ + vg_settings.temp_audio_choice = -2; + + for( int i=0; ialias, vg_audio.device_choice.buffer ) ){ + vg_settings.temp_audio_choice = oi->value; + break; + } + } + } + else { + vg_settings.temp_audio_choice = -1; + } + + ui_settings_enum_init( &vg_settings.audio_devices ); + ui_settings_enum_init( &vg_settings.dsp ); + +#ifdef VG_GAME_SETTINGS + vg_game_settings_init(); +#endif +} + +void vg_settings_close(void) +{ + vg.settings_open = 0; + + struct ui_enum_opt *options = vg_settings.audio_devices.options; + for( int i=1; i < vg_settings.audio_devices.option_count; i ++ ) + free( (void *)options[i].alias ); + free( vg_settings.audio_devices.options ); +} + +static void vg_settings_gui(void) +{ + ui_rect null; + ui_rect screen = { 0, 0, vg.window_x, vg.window_y }; + ui_rect window = { 0, 0, 1000, 700 }; + ui_rect_center( screen, window ); + vg_ui.wants_mouse = 1; + + ui_fill( window, ui_colour( k_ui_bg+1 ) ); + ui_outline( window, 1, ui_colour( k_ui_bg+7 ), 0 ); + + ui_rect title, panel; + ui_split( window, k_ui_axis_h, 28, 0, title, panel ); + ui_fill( title, ui_colour( k_ui_bg+7 ) ); + ui_text( title, "Settings", 1, k_ui_align_middle_center, + ui_colourcont(k_ui_bg+7) ); + + ui_rect quit_button; + ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button ); + + if( ui_button_text( quit_button, "X", 1 ) == 1 ){ + vg_settings_close(); + return; + } + + ui_rect_pad( panel, (ui_px[2]){ 8, 8 } ); + + const char *opts[] = { "video", "audio", +#ifdef VG_GAME_SETTINGS + "game" +#endif + }; + + static i32 page = 0; + ui_tabs( panel, panel, opts, vg_list_size(opts), &page ); + + if( page == 0 ){ + vg_settings_video_gui( panel ); + } + else if( page == 1 ) + vg_settings_audio_gui( panel ); + +#ifdef VG_GAME_SETTINGS + else if( page == 2 ) + vg_game_settings_gui( panel ); +#endif +} + +static int cmd_vg_settings_toggle( int argc, const char *argv[] ){ + vg_settings_open(); + return 0; +} + +/* + * Graphic cards will check these to force it to use the GPU. + * TODO: explicit declexport. since -flto strips these symbols in release. + */ +u32 NvOptimusEnablement = 0x00000001; +int AmdPowerXpressRequestHighPerformance = 1; + +#include "vg_async.c" +#include "vg_audio.c" +#include "vg_audio_dsp.c" +#include "vg_audio_synth_bird.c" +#include "vg_binstr.c" +#include "vg_bvh.c" +#include "vg_camera.c" +#include "vg_lines.c" +#include "vg_console.c" +#include "vg_imgui.c" +#include "vg_input.c" +#include "vg_io.c" +#include "vg_loader.c" +#include "vg_log.c" +#include "vg_tex.c" +#include "vg_mem.c" +#include "vg_mem_pool.c" +#include "vg_mem_queue.c" +#include "vg_msg.c" +#include "vg_opt.c" +#include "vg_perlin.c" +#include "vg_string.c" +#include "vg_profiler.c" +#include "vg_rigidbody_collision.c" +#include "vg_rigidbody_constraints.c" +#include "vg_rigidbody.c" +#include "vg_rigidbody_view.c" +#include "vg_shader.c" diff --git a/vg_engine.h b/vg_engine.h new file mode 100644 index 0000000..81bbda5 --- /dev/null +++ b/vg_engine.h @@ -0,0 +1,241 @@ +#pragma once + +/* Copyright (C) 2021-2024 Mt.Zero Software - All Rights Reserved */ +/* + .-. VG Event loop +| 0 | +| | .---------------------------------------------------------. +|API| | vg_enter( int argc, char *argv[], const char *window_name | +| | '---------------------------------------------------------' +| | | +| | v +|IMP| vg_launch_opt(void) <--. +| | | | +| | |'---------------' +| | | .-. +| | |'-----------------------------------| 1 |------. +| | | | | | +| | | | | v +| | | |IMP| vg_preload(void) +| | | | | | +| | .-----+. | | v +| | | | |IMP| vg_load(void) +| | | v '___' | +|IMP| | vg_framebuffer_resize(void) | +| | | | | +|IMP| | |.--------------------------------------------' +| | | | +| | | v +|IMP| | vg_pre_update(void) +| | | | +| | | .-----+. +| | | | | called 0x to 8x +| | | | v +|IMP| | '- vg_fixed_update(void) +| | | | +| | | .-' +| | | | +| | | v +|IMP| | vg_post_update(void) +| | | | +| | | v +|IMP| | vg_render(void) +| | | | +| | | v +|IMP| | vg_gui(void) +| | | | +| | | v +|IMP| | vg_game_settings_init(void) +|IMP| | vg_game_settings_gui( ui_rect panel ) +| | | | (optional: #define VG_GAME_SETTINGS) +| | | | +| | '----' +'___' + +*/ + +/* configuration warnings */ + +#ifndef VG_TIMESTEP_FIXED + #warning VG_TIMESTEP_FIXED not defined; setting to 1/60 + #define VG_TIMESTEP_FIXED (1.0/60.0) +#endif + +#ifndef VG_3D + #ifndef VG_2D + #warning VG_3D or VG_2D not defined; defining VG_3D + #define VG_3D + #endif +#endif + +#define VG_ENGINE +#define SDL_MAIN_HANDLED + +#include "dep/glad/glad.h" +#include "dep/sdl/include/SDL.h" + +#include "vg_platform.h" +#include "vg_mem.h" +#include "vg_m.h" +#include "vg_imgui.h" + +#include + +/* API */ +void vg_enter( int argc, char *argv[], const char *window_name ); + +/* Thread 1 */ +void vg_bake_shaders(void); + +extern void vg_preload(void); +extern void vg_load(void); + +/* Main thread */ +extern void vg_launch_opt(void); +extern void vg_framebuffer_resize(int w, int h); +extern void vg_pre_update(void); +extern void vg_fixed_update(void); +extern void vg_post_update(void); + +extern void vg_render(void); +extern void vg_gui(void); + +void vg_settings_open(void); +void vg_settings_close(void); + +enum quality_profile{ + k_quality_profile_high = 0, + k_quality_profile_low = 1, + k_quality_profile_min = 2 +}; + +enum vg_thread_purpose +{ + k_thread_purpose_nothing, + k_thread_purpose_main, + k_thread_purpose_loader +}; + +struct vg_engine { + /* Engine sync */ + SDL_Window *window; + SDL_GLContext gl_context; + + SDL_sem *sem_loader; /* allows only one loader at a time */ + jmp_buf env_loader_exit; + + SDL_threadID thread_id_main, + thread_id_loader; + void *thread_data; + + SDL_SpinLock sl_status; + enum engine_status{ + k_engine_status_none, + k_engine_status_load_internal, + k_engine_status_running, + k_engine_status_crashed + } + engine_status; + + /* Window information */ + int window_x, + window_y, + samples, + window_should_close; + const char *base_path; + + int display_refresh_rate, + fps_limit, + vsync, + screen_mode, + display_index; + + int settings_open; + + enum vsync_feature{ + k_vsync_feature_disabled=0, + k_vsync_feature_enabled=1, + k_vsync_feature_enabled_adaptive=2, + k_vsync_feature_error=3 + } + vsync_feature; + + i32 mouse_pos[2]; + v2f mouse_delta, + mouse_wheel; + + /* Runtime */ + double time, + time_real, + time_delta, + time_rate, + + time_fixed_accumulator, + time_fixed_extrapolate, + time_frame_delta; + + f32 time_fixed_delta; + + u64 time_hp, time_hp_last, time_spinning; + + int fixed_iterations; + + enum engine_stage{ + k_engine_stage_none, + k_engine_stage_update, + k_engine_stage_update_fixed, + k_engine_stage_rendering, + k_engine_stage_ui + } + engine_stage; + + /* graphics */ +#ifdef VG_3D + m4x4f pv; +#else + m3x3f pv; +#endif + + i32 quality_profile; + + float loader_ring; + GLuint tex_missing; + + vg_rand rand; +} +extern vg; + +struct vg_setting_enum +{ + i32 new_value, *actual_value; + + struct ui_enum_opt *options; + u32 option_count; + const char *label; +}; + +struct vg_setting_ranged_i32 +{ + i32 new_value, *actual_value, min, max; + char buf[10]; + const char *label; +}; + +void ui_settings_ranged_i32_init( struct vg_setting_ranged_i32 *prop ); +void ui_settings_enum_init( struct vg_setting_enum *prop ); +bool vg_settings_enum( struct vg_setting_enum *prop, ui_rect rect ); +bool vg_settings_enum_diff( struct vg_setting_enum *prop ); +void vg_settings_ui_header( ui_rect inout_panel, const char *name ); +bool vg_settings_apply_button( ui_rect inout_panel, bool validated ); + +enum engine_status _vg_engine_status(void); +enum vg_thread_purpose vg_thread_purpose(void); + +void vg_checkgl( const char *src_info ); +#define VG_STRINGIT( X ) #X +#define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) ) + +/* the few includes we always want. */ +#include "vg_log.h" +#include "vg_shader.h" +#include "vg_console.h" diff --git a/vg_graph.h b/vg_graph.h deleted file mode 100644 index ec7f974..0000000 --- a/vg_graph.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef VG_GRAPH_H -#define VG_GRAPH_H - -#define VG_GAME -#include "vg/vg.h" - -enum { k_vg_graph_max_samples = 1024 }; -enum { k_vg_graph_max_vertices = k_vg_graph_max_samples * 2 }; -enum { k_vg_graph_max_indices = (k_vg_graph_max_samples-1) * 6 }; - -struct vg_graph -{ - GLuint vao, vbo, ebo; -}; - -static void vg_graph_init( struct vg_graph *graph ) -{ - vg_acquire_thread_sync(); - { - glGenVertexArrays( 1, &graph->vao ); - glGenBuffers( 1, &graph->vbo ); - glGenBuffers( 1, &graph->ebo ); - glBindVertexArray( graph->vao ); - - size_t stride = sizeof(v2f); - - glBindBuffer( GL_ARRAY_BUFFER, graph->vbo ); - glBufferData( GL_ARRAY_BUFFER, k_vg_graph_max_vertices*stride, - NULL, GL_DYNAMIC_DRAW ); - - glBindVertexArray( graph->vao ); - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, graph->ebo ); - glBufferData( GL_ELEMENT_ARRAY_BUFFER, - k_vg_graph_max_indices*sizeof(u16), NULL, - GL_DYNAMIC_DRAW ); - - glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, stride, (void *)0 ); - glEnableVertexAttribArray( 0 ); - VG_CHECK_GL_ERR(); - } -} - -static void vg_graph_add_sample( struct vg_graph *graph ) -{ - -} - -#endif /* VG_GRAPH_H */ diff --git a/vg_image.h b/vg_image.h new file mode 100644 index 0000000..192909e --- /dev/null +++ b/vg_image.h @@ -0,0 +1,5 @@ +#pragma once +#include "vg/submodules/stb/stb_image_write.h" +#define STBI_ONLY_JPEG +#define STBI_NO_THREAD_LOCALS +#include "vg/submodules/stb/stb_image.h" diff --git a/vg_imgui.c b/vg_imgui.c new file mode 100644 index 0000000..2510404 --- /dev/null +++ b/vg_imgui.c @@ -0,0 +1,2206 @@ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ + +/* + * Principles: + * + * 1. layout is defined by subdividing + * 2. a parent node should never be resized by the content after creation + * 3. when the ui is in an interactive state, no controls should ever move + * 4. controls directly reference a memory location and use that as their + * unique id + * 5. a maximum of ONE control per memory location can be drawn at any given + * point. + */ + +#pragma once + +#include "vg_imgui.h" +#include "vg_engine.h" +#include "vg_tex.h" +#include "vg_shader.h" +#include + +ui_px k_ui_widget_height = 28, + k_ui_scale = 1, + k_ui_padding = 8; + +ui_font vg_ui_font_small = { + .glyph_width = 8, + .glyph_height = 14, + .glyph_baseline = 4, + .line_height = 14, + .sheet_size = 256, + .spacing = 8, + .ascii_start = ' ', + .offset_y = 0 +}, +vg_ui_font_big = { + .glyph_width = 12, + .glyph_height = 21, + .glyph_baseline = 6, + .line_height = 21, + .sheet_size = 256, + .spacing = 10, + .ascii_start = ' ', + .offset_y = 84 +}; + +struct vg_imgui vg_ui = { + .scheme = { + [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ), + [ k_ui_bg+1 ] = UI_RGB( 0x282828 ), + [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ), + [ k_ui_bg+3 ] = UI_RGB( 0x504945 ), + [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ), + [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ), + [ k_ui_bg+6 ] = UI_RGB( 0x928374 ), + [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ), + + [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ), + [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ), + [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ), + [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ), + [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ), + [ k_ui_fg+5 ] = UI_RGB( 0x000000 ), + [ k_ui_fg+6 ] = UI_RGB( 0x000000 ), + [ k_ui_fg+7 ] = UI_RGB( 0x000000 ), + + [ k_ui_red ] = UI_RGB( 0xcc241d ), + [ k_ui_orange ] = UI_RGB( 0xd65d0e ), + [ k_ui_yellow ] = UI_RGB( 0xd79921 ), + [ k_ui_green ] = UI_RGB( 0x98971a ), + [ k_ui_aqua ] = UI_RGB( 0x689d6a ), + [ k_ui_blue ] = UI_RGB( 0x458588 ), + [ k_ui_purple ] = UI_RGB( 0xb16286 ), + [ k_ui_gray ] = UI_RGB( 0x928374 ), + [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ), + [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ), + [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ), + [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ), + [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ), + [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ), + [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ), + [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ), + }, + .font = &vg_ui_font_small, + .colour = {1.0f,1.0f,1.0f,1.0f}, + .bg_inverse_ratio = {1,1} +}; + +static struct vg_shader _shader_ui ={ + .name = "[vg] ui - transparent", + .link = NULL, + .vs = { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + "uniform vec2 uBGInverseRatio;" + "" + "out vec4 aTexCoords;" + "out vec4 aColour;" + "" + "void main(){" + "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "gl_Position = proj_pos;" + "aTexCoords = vec4( a_uv * 0.00390625, " + " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );" + "aColour = a_colour;" + "}", + }, + .fs = { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexGlyphs;" + "uniform sampler2D uTexBG;" + "uniform vec4 uColour;" + "uniform float uSpread;" + "out vec4 FragColor;" + "" + "in vec4 aTexCoords;" + "in vec4 aColour;" + "" + "vec2 rand_hash22( vec2 p ){" + "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);" + "p3 += dot(p3, p3.yzx+19.19);" + "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));" + "}" + "" + "void main(){" + "vec4 diffuse = aColour;" + + "vec4 avg = vec4(0.0);" + + "if( aColour.a == 0.0 ){" + "avg = aColour;" + "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;" + "}" + "else{" + "if( uSpread > 0.0001 ){" + "for( int i=0; i<4; i ++ ){" + "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" + "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" + "}" + "avg *= 0.25;" + "avg.a = 1.0;" + "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" + "}" + "else{" + "avg = aColour;" + "}" + "}" + + "FragColor = avg * uColour;" + "}" + } +}; + +static struct vg_shader _shader_ui_image = { + .name = "[vg] ui_image", + .link = NULL, + .vs = + { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexImage;" + "uniform vec4 uColour;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec4 colour = texture( uTexImage, aTexCoords );" + "FragColor = colour * uColour;" + "}" + } +}; + +static struct vg_shader _shader_ui_hsv = { + .name = "[vg] ui_hsv", + .link = NULL, + .vs = { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform float uHue;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec3 c = vec3( uHue, aTexCoords );" + "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);" + "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);" + "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);" + "FragColor = vec4( colour, 1.0 );" + "}" + } +}; + +void vg_ui_init(void) +{ + if( !vg_shader_compile( &_shader_ui ) || + !vg_shader_compile( &_shader_ui_image ) || + !vg_shader_compile( &_shader_ui_hsv ) ){ + vg_fatal_error( "Failed to compile ui shader" ); + } + + /* + * Vertex buffer + * ---------------------------------------- + */ + + vg_ui.max_indices = 20000; + vg_ui.max_verts = 30000; + + /* Generate the buffer we are gonna be drawing to */ + glGenVertexArrays( 1, &vg_ui.vao ); + glGenBuffers( 1, &vg_ui.vbo ); + glGenBuffers( 1, &vg_ui.ebo ); + + glBindVertexArray( vg_ui.vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); + + glBufferData( GL_ARRAY_BUFFER, + vg_ui.max_verts * sizeof( struct ui_vert ), + NULL, GL_DYNAMIC_DRAW ); + glBindVertexArray( vg_ui.vao ); + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, + vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW ); + + VG_CHECK_GL_ERR(); + + /* Set pointers */ + u32 const stride = sizeof( struct ui_vert ); + + /* XY */ + glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride, + (void *)offsetof( struct ui_vert, co ) ); + glEnableVertexAttribArray( 0 ); + + /* UV */ + glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, + (void *)offsetof( struct ui_vert, uv ) ); + glEnableVertexAttribArray( 1 ); + + /* COLOUR */ + glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, + (void *)offsetof( struct ui_vert, colour ) ); + glEnableVertexAttribArray( 2 ); + + VG_CHECK_GL_ERR(); + + /* Alloc RAM default context */ + u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert), + inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) ); + + vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size ); + vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size ); + + /* font + * ----------------------------------------------------- + */ + + /* Load default font */ + u32 compressed[] = { + #include "vg/vg_pxfont_thin.h" + }; + + u32 pixels = 0, total = 256*256, data = 0; + u8 image[256*256]; + + while( pixels < total ){ + for( int b = 31; b >= 0; b-- ){ + image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u; + + if( pixels >= total ){ + total = 0; + break; + } + } + data++; + } + + glGenTextures( 1, &vg_ui.tex_glyphs ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0, + GL_RED, GL_UNSIGNED_BYTE, image ); + + VG_CHECK_GL_ERR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + /* + * Cursors + * --------------------------------------------------------------- + */ + + vg_ui.cursor_map[ k_ui_cursor_default ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); + vg_ui.cursor_map[ k_ui_cursor_hand ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); + vg_ui.cursor_map[ k_ui_cursor_ibeam ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); +} + +void rect_copy( ui_rect a, ui_rect b ){ + for( int i=0; i<4; i++ ) + b[i] = a[i]; +} + +void ui_flush( enum ui_shader shader, f32 w, f32 h ){ + u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert), + vertex_count = vg_ui.cur_vert-vg_ui.vert_start, + vertex_size = vertex_count*sizeof(ui_vert), + + indice_offset = vg_ui.indice_start*sizeof(u16), + indice_count = vg_ui.cur_indice-vg_ui.indice_start, + indice_size = indice_count * sizeof(u16); + + if( !vertex_size || !indice_size ) + return; + + glBindVertexArray( vg_ui.vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); + glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size, + vg_ui.vertex_buffer+vg_ui.vert_start ); + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); + glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size, + vg_ui.indice_buffer+vg_ui.indice_start ); + + glDisable( GL_DEPTH_TEST ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glBlendEquation( GL_FUNC_ADD ); + glDisable( GL_CULL_FACE ); + + m3x3f view = M3X3_IDENTITY; + m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } ); + m3x3_scale( view, (v3f){ 1.0f/(w*0.5f), + -1.0f/(h*0.5f), 1.0f } ); + + if( shader == k_ui_shader_colour ){ + glUseProgram( _shader_ui.id ); + + glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1, + GL_FALSE, (float *)view ); + glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, + vg_ui.colour ); + + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); + + glActiveTexture( GL_TEXTURE1 ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); + glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), + vg_ui.frosting ); + glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), + 1, vg_ui.bg_inverse_ratio ); + } + else if( shader == k_ui_shader_image ){ + glUseProgram( _shader_ui_image.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1, + GL_FALSE, (float *)view ); + glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 ); + glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, + vg_ui.colour ); + } + else if( shader == k_ui_shader_hsv ){ + glUseProgram( _shader_ui_hsv.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, + GL_FALSE, (float *)view ); + glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), vg_ui.hue ); + } + else + vg_fatal_error( "Invalid UI shader (%d)\n", shader ); + + glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT, + (void *)(vg_ui.indice_start*sizeof(u16)) ); + + glDisable( GL_BLEND ); + + vg_ui.indice_start = vg_ui.cur_indice; + vg_ui.vert_start = vg_ui.cur_vert; +} + +void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ) +{ + /* this if far from ideal but stops us from crashing */ + if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) || + (vg_ui.cur_indice + 6 > vg_ui.max_indices)) + return; + + struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; + u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; + + for( int i=0; i<4; i++ ){ + vertices[i].colour = colour; + } + + vertices[0].co[0] = rect[0]; + vertices[0].co[1] = rect[1]; + vertices[0].uv[0] = uv[0]; + vertices[0].uv[1] = uv[1]; + vertices[1].co[0] = rect[0]+rect[2]; + vertices[1].co[1] = rect[1]; + vertices[1].uv[0] = uv[2]; + vertices[1].uv[1] = uv[1]; + vertices[2].co[0] = rect[0]+rect[2]; + vertices[2].co[1] = rect[1]+rect[3]; + vertices[2].uv[0] = uv[2]; + vertices[2].uv[1] = uv[3]; + vertices[3].co[0] = rect[0]; + vertices[3].co[1] = rect[1]+rect[3]; + vertices[3].uv[0] = uv[0]; + vertices[3].uv[1] = uv[3]; + u16 ind_start = vg_ui.cur_vert; + + u16 start = vg_ui.cur_vert; + u32 mesh[] = { 0,2,1, 0,3,2 }; + + for( u32 i=0; i vg_ui.max_verts) || + (vg_ui.cur_indice + 24 > vg_ui.max_indices)) + return; + + struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; + u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; + + for( int i=0; i<8; i++ ){ + vertices[i].uv[0] = 4; + vertices[i].uv[1] = 4; + vertices[i].colour = colour; + } + + vertices[0].co[0] = rect[0]; + vertices[0].co[1] = rect[1]; + vertices[1].co[0] = rect[0]+rect[2]; + vertices[1].co[1] = rect[1]; + vertices[2].co[0] = rect[0]+rect[2]; + vertices[2].co[1] = rect[1]+rect[3]; + vertices[3].co[0] = rect[0]; + vertices[3].co[1] = rect[1]+rect[3]; + vertices[4].co[0] = vertices[0].co[0]-thickness; + vertices[4].co[1] = vertices[0].co[1]-thickness; + vertices[5].co[0] = vertices[1].co[0]+thickness; + vertices[5].co[1] = vertices[1].co[1]-thickness; + vertices[6].co[0] = vertices[2].co[0]+thickness; + vertices[6].co[1] = vertices[2].co[1]+thickness; + vertices[7].co[0] = vertices[3].co[0]-thickness; + vertices[7].co[1] = vertices[3].co[1]+thickness; + + u16 start = vg_ui.cur_vert; + u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 }; + + if( !mask ) + mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT; + + u32 c = 0; + for( u32 i=0; i rp ) dir = k_ui_axis_h; + else dir = k_ui_axis_v; + other = dir ^ 0x1; + + d[2+dir] = rect[2+dir]; + d[2+other] = (rect[2+dir] * size[other]) / size[dir]; + + ui_rect_center( rect, d ); +} + +void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, + ui_px gap, ui_rect l, ui_rect r ) +{ + ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; + ui_split( rect, dir, width, gap, l, r ); +} + +void ui_rect_pad( ui_rect rect, ui_px pad[2] ) +{ + rect[0] += pad[0]; + rect[1] += pad[1]; + rect[2] -= pad[0]*2; + rect[3] -= pad[1]*2; +} + +ui_px ui_text_line_width( const char *str ) +{ + int length = 0; + const char *_c = str; + u8 c; + + while( (c = *(_c ++)) ){ + if( c >= 32 ) length ++; + else if( c == '\n' ) break; + } + + return length * vg_ui.font->spacing; +} + +ui_px ui_text_string_height( const char *str ) +{ + int height = 1; + const char *_c = str; + u8 c; + + while( (c = *(_c ++)) ){ + if( c == '\n' ) height ++; + } + + return height * 14; +} + +ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, + enum ui_align align ) +{ + enum ui_align lwr = k_ui_align_lwr & align; + if( lwr == k_ui_align_left ){ + return rect[0]; + } + else{ + ui_px width = ui_text_line_width( str ) * scale; + + if( lwr == k_ui_align_right ) + return rect[0] + rect[2]-width; + else + return rect[0] + (rect[2]-width)/2; + } +} + +static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } +static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ){ + return ui_min( max, ui_max( a, min ) ); +} + +int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ) +{ + ui_px parent_max[2], child_max[2]; + parent_max[0] = parent[0]+parent[2]; + parent_max[1] = parent[1]+parent[3]; + child_max[0] = child[0]+child[2]; + child_max[1] = child[1]+child[3]; + + clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] ); + clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] ); + clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] ); + clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] ); + + if( clipped[0] == clipped[2] || + clipped[1] == clipped[3] ) + return 0; + + clipped[2] -= clipped[0]; + clipped[3] -= clipped[1]; + + return 1; +} + +int ui_inside_rect( ui_rect rect, ui_px co[2] ) +{ + if( co[0] >= rect[0] && + co[1] >= rect[1] && + co[0] < rect[0]+rect[2] && + co[1] < rect[1]+rect[3] ){ + return 1; + } + else + return 0; +} + +int ui_click_down( u32 mask ) +{ + if( vg_ui.ignore_input_frames ) return 0; + if( (vg_ui.mouse_state[0] & mask) && + !(vg_ui.mouse_state[1] & mask) ) + return 1; + else + return 0; +} + +int ui_clicking( u32 mask ) +{ + if( vg_ui.ignore_input_frames ) return 0; + return vg_ui.mouse_state[0] & mask; +} + +int ui_click_up( u32 mask ) +{ + if( vg_ui.ignore_input_frames ) return 0; + if( (vg_ui.mouse_state[1] & mask) && + !(vg_ui.mouse_state[0] & mask) ) + return 1; + else + return 0; +} + +void ui_prerender(void) +{ + int x, y; + vg_ui.mouse_state[1] = vg_ui.mouse_state[0]; + vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y ); + vg_ui.mouse_delta[0] = x-vg_ui.mouse[0]; + vg_ui.mouse_delta[1] = y-vg_ui.mouse[1]; + vg_ui.mouse[0] = x; + vg_ui.mouse[1] = y; + + vg_ui.cur_vert = 0; + vg_ui.cur_indice = 0; + vg_ui.vert_start = 0; + vg_ui.indice_start = 0; + vg_ui.focused_control_hit = 0; + vg_ui.cursor = k_ui_cursor_default; + vg_ui.wants_mouse = 0; + + if( vg_ui.ignore_input_frames ){ + vg_ui.ignore_input_frames --; + return; + } + + if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE) ){ + vg_ui.mouse_click[0] = vg_ui.mouse[0]; + vg_ui.mouse_click[1] = vg_ui.mouse[1]; + } +} + +u32 ui_colour( enum ui_scheme_colour id ) +{ + return vg_ui.scheme[ id ]; +} + +/* get an appropriately contrasting colour given the base */ +u32 ui_colourcont( enum ui_scheme_colour id ) +{ + if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg ); + else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 ); + else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 ); + else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg ); + else return ui_colour( k_ui_fg+1 ); +} + +void ui_hex_to_norm( u32 hex, v4f norm ) +{ + norm[0] = ((hex ) & 0xff); + norm[1] = ((hex>>8 ) & 0xff); + norm[2] = ((hex>>16) & 0xff); + norm[3] = ((hex>>24) & 0xff); + v4_muls( norm, 1.0f/255.0f, norm ); +} + +u32 v4f_u32_colour( v4f colour ) +{ + u32 r = colour[0] * 255.0f, + g = colour[1] * 255.0f, + b = colour[2] * 255.0f, + a = colour[3] * 255.0f; + + return r | (g<<8) | (b<<16) | (a<<24); +} + +static void ui_text_glyph( const struct ui_font *font, ui_px scale, + u8 glyph, ui_rect out_texcoords ){ + glyph -= font->ascii_start; + + ui_px per_row = font->sheet_size / font->glyph_width, + column = (ui_px)glyph % per_row, + row = (glyph - column) / per_row; + + out_texcoords[0] = column * font->glyph_width; + out_texcoords[1] = row * font->glyph_height + font->offset_y; + out_texcoords[2] = out_texcoords[0] + font->glyph_width; + out_texcoords[3] = out_texcoords[1] + font->glyph_height; +} + +u32 ui_opacity( u32 colour, f32 opacity ) +{ + u32 alpha = opacity * 255.0f; + return (colour & 0x00ffffff) | (alpha << 24); +} + +u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ) +{ + ui_rect text_cursor; + if( colour == 0 ) colour = ui_colour( k_ui_fg ); + + colour &= 0x00ffffff; + + const char *_c = str; + u8 c; + + text_cursor[0] = ui_text_aligned_x( str, rect, scale, align ); + text_cursor[1] = rect[1]; + text_cursor[2] = vg_ui.font->glyph_width*scale; + text_cursor[3] = vg_ui.font->glyph_height*scale; + + u32 printed_chars = 0; + + if( align & (k_ui_align_middle|k_ui_align_bottom) ){ + ui_px height = ui_text_string_height( str ) * scale; + + if( align & k_ui_align_bottom ) + text_cursor[1] += rect[3]-height; + else + text_cursor[1] += (rect[3]-height)/2; + } + + while( (c = *(_c ++)) ){ + if( printed_chars >= len ){ + printed_chars = 0; + text_cursor[1] += vg_ui.font->line_height*scale; + text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); + text_cursor[0] -= vg_ui.font->spacing*scale; + + ui_rect glyph; + ui_text_glyph( vg_ui.font, scale, '\xb6' /*FIXME*/, glyph ); + ui_fill_rect( text_cursor, 0x00ffffff, glyph ); + text_cursor[0] += vg_ui.font->spacing*scale; + } + + if( c == '\n' ){ + text_cursor[1] += vg_ui.font->line_height*scale; + text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); + printed_chars = 0; + continue; + } + else if( c >= 33 ){ + ui_rect glyph; + ui_text_glyph( vg_ui.font, scale, c, glyph ); + + ui_rect cursor_clipped; + if( ui_clip( rect, text_cursor, cursor_clipped ) ){ + ui_fill_rect( cursor_clipped, colour, glyph ); + } + } + else if( c == '\x1B' ){ + /* vt codes */ + _c ++; + u16 colour_id = 0; + for( int i=0; i<3; i ++ ){ + if( _c[i] ){ + if( _c[i] == 'm' ){ + _c = _c + i + 1; + + switch( colour_id ){ + case '0': colour = ui_colour( k_ui_fg ); break; + case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break; + case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break; + case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break; + case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break; + case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break; + case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break; + case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break; + case '3'|'7'<<8: colour = 0xffffffff; break; + } + + colour &= 0x00ffffff; + break; + } + + colour_id |= _c[i] << (i*8); + } + else{ + _c = _c +i; + break; + } + } + + continue; + } + else if( c == '\t' ){ + text_cursor[0] += vg_ui.font->spacing*scale*4; + printed_chars += 4; + continue; + } + + text_cursor[0] += vg_ui.font->spacing*scale; + printed_chars ++; + } + + return printed_chars; +} + +void ui_text( ui_rect rect, const char *str, ui_px scale, + enum ui_align align, u32 colour ) +{ + ui_ntext( rect, str, 1024, scale, align, colour ); +} + +/* + * Standard layout stuff + * ----------------------------------------------------------------------------- + */ + +void ui_panel( ui_rect in_rect, ui_rect out_panel ) +{ + ui_fill( in_rect, ui_colour( k_ui_bg+1 ) ); + ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 ); + rect_copy( in_rect, out_panel ); + ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); +} + +void ui_label( ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ) +{ + ui_rect l; + ui_px width = (ui_text_line_width(text)+vg_ui.font->spacing) * size; + ui_split( rect, k_ui_axis_v, width, gap, l, r ); + ui_text( l, text, 1, k_ui_align_middle_left, 0 ); +} + +void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ) +{ + ui_px height = (count * vg_ui.font->glyph_height + 18) * k_ui_scale; + ui_split( inout_panel, k_ui_axis_h, height, k_ui_padding, + out_rect, inout_panel ); +} + +void ui_info( ui_rect inout_panel, const char *text ) +{ + ui_rect box; + ui_standard_widget( inout_panel, box, 1 ); + ui_text( box, text, 1, k_ui_align_middle_left, 0 ); +} + +void ui_image( ui_rect rect, GLuint image ) +{ + ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, image ); + ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + ui_flush( k_ui_shader_image, vg.window_x, vg.window_y ); +} + +void ui_defocus_all(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + SDL_StopTextInput(); + if( vg_ui.textbox.callbacks.escape ) + vg_ui.textbox.callbacks.escape(); + } + + vg_ui.focused_control_id = NULL; + vg_ui.focused_control_hit = 0; + vg_ui.focused_control_type = k_ui_control_none; +} + +/* TODO: split this out into a formatless button and one that auto fills */ +enum ui_button_state ui_colourbutton( ui_rect rect, + enum ui_scheme_colour colour, + enum ui_scheme_colour hover_colour, + enum ui_scheme_colour hi_colour, + bool const fill ) +{ + int clickup= ui_click_up(UI_MOUSE_LEFT), + click = ui_clicking(UI_MOUSE_LEFT) | clickup, + target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, + hover = ui_inside_rect( rect, vg_ui.mouse ); + + u32 col_base = vg_ui.scheme[ colour ], + col_highlight = vg_ui.scheme[ hi_colour? hi_colour: k_ui_fg ], + col_hover = vg_ui.scheme[ hover_colour? hover_colour: + colour + k_ui_brighter ]; + + if( vg_ui.focused_control_type != k_ui_control_none ){ + clickup = 0; + click = 0; + target = 0; + hover = 0; + } + + if( hover ){ + vg_ui.cursor = k_ui_cursor_hand; + } + + if( click ){ + if( target ){ + if( hover ){ + if( clickup ){ + vg_ui.ignore_input_frames = 2; + ui_defocus_all(); + + if( fill ) { + ui_fill( rect, col_highlight ); + rect_copy( rect, vg_ui.click_fader ); + rect_copy( rect, vg_ui.click_fader_end ); + vg_ui.click_fader_end[3] = 0; + ui_rect_center( rect, vg_ui.click_fader_end ); + vg_ui.click_fade_opacity = 1.0f; + } + + return k_ui_button_click; + } + else{ + if( fill ) ui_fill( rect, col_highlight ); + return k_ui_button_holding_inside; + } + } + else{ + if( fill ) ui_fill( rect, col_base ); + ui_outline( rect, 1, col_highlight, 0 ); + return k_ui_button_holding_outside; + } + } + else{ + if( fill ) ui_fill( rect, col_base ); + return k_ui_button_none; + } + } + else{ + if( hover ){ + if( fill ) ui_fill( rect, col_hover ); + return k_ui_button_hover; + } + else{ + if( fill ) ui_fill( rect, col_base ); + return k_ui_button_none; + } + } +} + +enum ui_button_state ui_colourbutton_text( + ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ){ + enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0, 1 ); + ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale }; + ui_rect_center( rect, t ); + + u32 text_colour = ui_colourcont(colour); + if( state == k_ui_button_holding_inside ) + text_colour = colour; + + ui_text( t, string, scale, k_ui_align_left, text_colour ); + return state; +} + +enum ui_button_state ui_button_text( ui_rect rect, + const char *string, ui_px scale ) +{ + return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 ); +} + +enum ui_button_state ui_button( ui_rect inout_panel, const char *string ) +{ + ui_rect rect; + ui_standard_widget( inout_panel, rect, 1 ); + return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 ); +} + +static void ui_enum_post(void); +void ui_postrender(void) +{ + if( vg_ui.click_fade_opacity > 0.0f ){ + float scale = vg_ui.click_fade_opacity; + scale = vg_maxf( 1.0f/255.0f, scale*scale ); + + vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f; + u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; + + v4f begin, end, dest; + for( int i=0; i<4; i++ ){ + begin[i] = vg_ui.click_fader[i]; + end[i] = vg_ui.click_fader_end[i]+1; + } + + v4_lerp( end, begin, scale, dest ); + + ui_rect rect; + for( int i=0; i<4; i++ ){ + rect[i] = dest[i]; + } + + ui_fill( rect, colour ); + } + + if( vg_ui.focused_control_type == k_ui_control_enum ){ + ui_enum_post(); + } + else if( vg_ui.focused_control_type == k_ui_control_modal ){ + ui_rect screen = {0,0,vg.window_x,vg.window_y}; + ui_fill( screen, 0xa0000000 ); + ui_rect box = {0,0,400,200}; + + u32 colour = ui_colour(k_ui_fg), + type = vg_ui.modal.options & UI_MODAL_TYPE_BITS; + if ( type == 1 ) colour = ui_colour(k_ui_green); + else if( type == 2 ) colour = ui_colour(k_ui_red); + else if( type == 3 ) colour = ui_colour(k_ui_yellow); + + ui_rect_center( screen, box ); + ui_fill( box, ui_colour(k_ui_bg) ); + ui_outline( box, -1, colour, 0 ); + + ui_rect message; + rect_copy( box, message ); + message[3] = 100; + ui_rect_center( box, message ); + + ui_rect row0, row1, btn; + ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 ); + row0[0] += vg_ui.font->spacing; + ui_ntext( row0, vg_ui.modal.message, (box[2]/vg_ui.font->spacing)-2, 1, + k_ui_align_left, colour ); + + rect_copy( row1, btn ); + btn[2] = 86; + btn[3] = 28; + ui_rect_center( row1, btn ); + + vg_ui.focused_control_type = k_ui_control_none; /* HACK */ + if( ui_button_text( btn, "OK", 1 ) != 1 ) + vg_ui.focused_control_hit = 1; + vg_ui.focused_control_type = k_ui_control_modal; /* HACK */ + vg_ui.wants_mouse = 1; + } + + ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); + + if( !vg_ui.focused_control_hit ){ + ui_defocus_all(); + } + + if( vg_ui.wants_mouse ){ + SDL_SetWindowGrab( vg.window, SDL_FALSE ); + SDL_SetRelativeMouseMode( SDL_FALSE ); + } + else{ + SDL_SetWindowGrab( vg.window, SDL_TRUE ); + SDL_SetRelativeMouseMode( SDL_TRUE ); + } + + SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] ); + SDL_ShowCursor(1); +} + +/* + * checkbox + * ----------------------------------------------------------------------------- + */ + +int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ) +{ + ui_rect rect, label, box; + ui_standard_widget( inout_panel, rect, 1 ); + + ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); + ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 ); + + int changed = ui_colourbutton( box, k_ui_bg, 0, 0, 1 )==1; + if( changed ) + *data = (*data) ^ 0x1; + + if( *data ){ + ui_rect_pad( box, (ui_px[2]){4,4} ); + ui_fill( box, ui_colour( k_ui_orange ) ); + } + + return changed; +} + +/* + * Dropdown / Enum + * ----------------------------------------------------------------------------- + */ + +/* + * unfortunately no return value since we only find out that event in the + * postrender step. + */ +void ui_enum( ui_rect inout_panel, const char *str_label, + struct ui_enum_opt *options, u32 len, i32 *value ) +{ + ui_rect rect, label, box; + ui_standard_widget( inout_panel, rect, 1 ); + ui_label( rect, str_label, k_ui_scale, 0, box ); + + const char *display = "OUT OF RANGE"; + int valid = 0; + for( u32 i=0; iterminator to start */ + int remaining_length = strlen( vg_ui.textbuf )+1-end; + memmove( &vg_ui.textbuf[ start ], + &vg_ui.textbuf[ end ], + remaining_length ); + return start; +} + +static void _ui_textbox_to_clipboard(void){ + int start, end; + _ui_textbox_make_selection( &start, &end ); + char buffer[512]; + + if( end-start ){ + memcpy( buffer, &vg_ui.textbuf[ start ], end-start ); + buffer[ end-start ] = 0x00; + SDL_SetClipboardText( buffer ); + } +} + +static void _ui_textbox_change_callback(void){ + if( vg_ui.textbox.callbacks.change ){ + vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); + + /* we gave permission to modify the buffer in this callback so.. */ + int len = strlen( vg_ui.textbuf ); + vg_ui.textbox.cursor_user = VG_MIN( vg_ui.textbox.cursor_user, len ); + vg_ui.textbox.cursor_pos = VG_MIN( vg_ui.textbox.cursor_pos, len ); + } +} + +void ui_start_modal( const char *message, u32 options ); +static void _ui_textbox_clipboard_paste(void){ + if( !SDL_HasClipboardText() ) + return; + + char *text = SDL_GetClipboardText(); + + if( !text ) + return; + + int datastart = _ui_textbox_delete_char( 0 ); + int length = strlen( text ); + + if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) < length ){ + ui_start_modal( "Clipboard content exceeds buffer size.", UI_MODAL_BAD ); + return; + } + + int cpylength = _ui_textbox_makeroom( datastart, length ); + + memcpy( vg_ui.textbuf + datastart, text, cpylength); + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, cpylength, 1 ); + SDL_free( text ); + _ui_textbox_change_callback(); +} + +void _ui_textbox_put_char( char c ) +{ + vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); + if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) <= 1 ) return; + + if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) ) + vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c; + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, 1, 1 ); +} + +/* Receed secondary cursor */ +void _ui_textbox_left_select(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 ); +} + +/* Match and receed both cursors */ +void _ui_textbox_left(void) +{ + int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, -cursor_diff, 1 ); +} + +void _ui_textbox_up(void) +{ + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + int line_begin = vg_ui.textbox.cursor_user; + + while( line_begin ){ + if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ + break; + } + + line_begin --; + } + + if( line_begin ){ + int line_above_begin = line_begin-1; + + while( line_above_begin ){ + if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){ + break; + } + + line_above_begin --; + } + + int offset = vg_ui.textbox.cursor_user - line_begin, + line_length_above = line_begin - line_above_begin -1; + + offset = VG_MIN( line_length_above, offset ); + + vg_ui.textbox.cursor_user = line_above_begin+offset; + vg_ui.textbox.cursor_pos = line_above_begin+offset; + } + else{ + vg_ui.textbox.cursor_user = line_begin; + vg_ui.textbox.cursor_pos = line_begin; + } + } + else{ + if( vg_ui.textbox.callbacks.up ){ + vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +void _ui_textbox_down(void) +{ + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + int line_begin = vg_ui.textbox.cursor_user; + + while( line_begin ){ + if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ + break; + } + + line_begin --; + } + + int line_below_begin = vg_ui.textbox.cursor_user; + + while(1){ + if( vg_ui.textbuf[ line_below_begin ] == '\0' ){ + vg_ui.textbox.cursor_user = line_below_begin; + vg_ui.textbox.cursor_pos = line_below_begin; + return; + } + + if( vg_ui.textbuf[ line_below_begin ] == '\n' ){ + line_below_begin ++; + break; + } + + line_below_begin ++; + } + + int line_below_end = line_below_begin; + while(1){ + if( vg_ui.textbuf[ line_below_end ] == '\0' || + vg_ui.textbuf[ line_below_end ] == '\n' ){ + line_below_end ++; + break; + } + line_below_end ++; + } + + int offset = vg_ui.textbox.cursor_user - line_begin, + line_length_below = line_below_end - line_below_begin -1; + + offset = VG_MIN( line_length_below, offset ); + + vg_ui.textbox.cursor_user = line_below_begin+offset; + vg_ui.textbox.cursor_pos = line_below_begin+offset; + } + else{ + if( vg_ui.textbox.callbacks.down ){ + vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +void _ui_textbox_right_select(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 ); +} + +void _ui_textbox_right(void) +{ + int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, +cursor_diff, 1 ); +} + +void _ui_textbox_backspace(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 ); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; + _ui_textbox_change_callback(); + } +} + +void _ui_textbox_delete(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 ); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; + _ui_textbox_change_callback(); + } +} + +void _ui_textbox_home_select(void) +{ + i32 start = vg_ui.textbox.cursor_user; + + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + while( start ){ + if( vg_ui.textbuf[start-1] == '\n' ) + break; + else + start --; + } + } + else + start = 0; + + vg_ui.textbox.cursor_user = start; +} + +void _ui_textbox_home(void) +{ + _ui_textbox_home_select(); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; +} + +void _ui_textbox_end_select(void) +{ + i32 end = vg_ui.textbox.cursor_user; + + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + while( vg_ui.textbuf[end] ){ + if( vg_ui.textbuf[end] == '\n' ) + break; + else + end ++; + } + } + else + end = VG_MIN( vg_ui.textbox.len-1, strlen(vg_ui.textbuf) ); + + vg_ui.textbox.cursor_user = end; +} + +void _ui_textbox_end(void) +{ + _ui_textbox_end_select(); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; +} + +void _ui_textbox_select_all(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0); + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0); +} + +void _ui_textbox_cut(void) +{ + _ui_textbox_to_clipboard(); + vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; + _ui_textbox_change_callback(); +} + +void _ui_textbox_enter(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + vg_ui.ignore_input_frames = 2; + + if( vg_ui.textbox.callbacks.enter ) + vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len ); + + if( vg_ui.focused_control_type != k_ui_control_textbox ) return; + + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + _ui_textbox_put_char( '\n' ); + _ui_textbox_change_callback(); + } + else{ + if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) + ui_defocus_all(); + } + } +} + +/* + * based on a visual character coordinate relative to the anchor of the textbox, + * this works out the linear place in the buffer that coordinate maps to + * + * input coordinates go in co[0], co[1], and the result index is in co[2] + */ +static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){ + int i[3] = {0,0,0}; + + char c; + while( (c = vg_ui.textbuf[i[2]]) ){ + if( i[1]==co[1] && i[0]>=co[0] ) break; + + if( i[0] >= wrap_length ){ + i[1] ++; + i[0] = 0; + } + + if( c >= 32 && c <= 126 ){ + i[0] ++; + i[2] ++; + } + else if( c == '\n' ){ + i[1] ++; + + if( i[1] > co[1] ) break; + + i[2] ++; + i[0] = 0; + } + else i[2] ++; + } + + co[0] = i[0]; + co[1] = i[1]; + co[2] = i[2]; +} + +/* + * based on the index specied in co[2], work out the visual character + * coordinates and store them in co[0], co[1] + */ +static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){ + co[0] = 0; + co[1] = 0; + + char c; + int i=0; + + while( (c = vg_ui.textbuf[i ++]) ){ + if( i > co[2] ) break; + if( co[0] >= wrap_length ){ + co[1] ++; + co[0] = 0; + } + if( c >= 32 && c <= 126 ) co[0] ++; + else if( c == '\n' ){ + co[1] ++; + co[0] = 0; + } + } +} + +/* + * calculate the number of characters remaining until either: + * - the wrap_length limit is hit + * - end of the line/string + * + * index must be fully populated with visual X/Y, and linear index + */ +static int _ui_textbox_run_remaining( int index[3], int wrap_length ){ + int i=0, printed_chars=0; + char c; + while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){ + if( index[0]+i >= wrap_length ) break; + if( c >= 32 && c <= 126 ) printed_chars ++; + else if( c == '\n' ) break; + } + + return printed_chars+1; +} + +int ui_textbox( ui_rect inout_panel, const char *label, + char *buf, u32 len, u32 lines, u32 flags, + struct ui_textbox_callbacks *callbacks ) +{ + if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; + + ui_rect rect; + ui_standard_widget( inout_panel, rect, lines ); + + if( label ) + ui_label( rect, label, 1, 0, rect ); + + int clickup= ui_click_up(UI_MOUSE_LEFT), + clickdown = ui_click_down(UI_MOUSE_LEFT), + click = ui_clicking(UI_MOUSE_LEFT) | clickup, + target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, + hover = ui_inside_rect( rect, vg_ui.mouse ); + + /* allow instant transitions from textbox->textbox */ + if( (vg_ui.focused_control_type != k_ui_control_none) && + (vg_ui.focused_control_type != k_ui_control_textbox) ){ + clickup = 0; + clickdown = 0; + click = 0; + target = 0; + hover = 0; + flags &= ~UI_TEXTBOX_AUTOFOCUS; + } + + u32 col_base = ui_colour( k_ui_bg ), + col_highlight = ui_colour( k_ui_fg ), + col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; + + ui_px border = -1; + + ui_rect text_rect; + rect_copy( rect, text_rect ); + + if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; + else text_rect[3] = vg_ui.font->line_height; + + text_rect[2] -= 16; + ui_rect_center( rect, text_rect ); + + ui_px wrap_length = 1024; + + if( flags & UI_TEXTBOX_WRAP ) + wrap_length = text_rect[2] / vg_ui.font->spacing; + + if( hover ){ + vg_ui.cursor = k_ui_cursor_ibeam; + } + + if( vg_ui.focused_control_id == buf ){ + ui_fill( rect, col_base ); + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); + + if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)){ + ui_defocus_all(); + } + else{ + vg_ui.focused_control_hit = 1; + if( click && target ){ + int p0[3] ={ + (vg_ui.mouse_click[0] - text_rect[0]) / vg_ui.font->spacing, + (vg_ui.mouse_click[1] - text_rect[1]) / vg_ui.font->line_height, + -1 + }, + p1[3] = { + (vg_ui.mouse[0] - text_rect[0]) / vg_ui.font->spacing, + (vg_ui.mouse[1] - text_rect[1]) / vg_ui.font->line_height, + -1 + }; + + if( flags & UI_TEXTBOX_MULTILINE ){ + _ui_textbox_calc_index_from_grid( p0, wrap_length ); + _ui_textbox_calc_index_from_grid( p1, wrap_length ); + + vg_ui.textbox.cursor_pos = p0[2]; + vg_ui.textbox.cursor_user = p1[2]; + } + else{ + int max = strlen( buf ); + vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), + vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); + } + } + + ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ], 0 ); + + ui_rect cursor; + + int c0 = vg_ui.textbox.cursor_pos, + c1 = vg_ui.textbox.cursor_user, + start = VG_MIN( c0, c1 ), + end = VG_MAX( c0, c1 ), + chars = end-start; + + if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ){ + int pos[3], remaining = chars; + + pos[2] = start; + _ui_textbox_index_calc_coords( pos, wrap_length ); + + if( start==end ){ + cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = 2; + cursor[3] = 13; + ui_fill( cursor, col_cursor ); + rect_copy( cursor, vg_ui.click_fader_end ); + } + else{ + while( remaining ){ + int run = _ui_textbox_run_remaining( pos, wrap_length ); + run = VG_MIN( run, remaining ); + + cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = (float)(run)*(float)vg_ui.font->spacing; + cursor[3] = 13; + + ui_fill( cursor, col_cursor ); + + remaining -= run; + pos[0] = 0; + pos[1] ++; + pos[2] += run; + } + rect_copy( cursor, vg_ui.click_fader_end ); + } + } + else{ + cursor[0] = text_rect[0] + start*vg_ui.font->spacing-1; + cursor[1] = text_rect[1]; + cursor[3] = 13; + + if( start==end ){ + cursor[2] = 2; + } + else{ + cursor[2] = (float)(chars)*(float)vg_ui.font->spacing; + } + + if( (vg_ui.click_fade_opacity<=0.0f) && + ui_clip( rect, cursor, cursor ) ){ + ui_fill( cursor, col_cursor ); + } + + rect_copy( cursor, vg_ui.click_fader_end ); + } + } + + return 0; + } + + if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ){ + if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ){ + ui_defocus_all(); + + ui_fill( rect, col_highlight ); + vg_ui.ignore_input_frames = 2; + rect_copy( rect, vg_ui.click_fader ); + rect_copy( rect, vg_ui.click_fader_end ); + + vg_ui.click_fade_opacity = 1.0f; + vg_ui.textbuf = buf; + vg_ui.focused_control_hit = 1; + vg_ui.focused_control_type = k_ui_control_textbox; + vg_ui.textbox.len = len; + vg_ui.textbox.flags = flags; + vg_ui.textbox.cursor_pos = 0; + vg_ui.textbox.cursor_user = 0; + + if( callbacks ){ + vg_ui.textbox.callbacks = *callbacks; + } + else{ + vg_ui.textbox.callbacks.change = NULL; + vg_ui.textbox.callbacks.down = NULL; + vg_ui.textbox.callbacks.up = NULL; + vg_ui.textbox.callbacks.enter = NULL; + } + + SDL_StartTextInput(); + } + } + + ui_fill( rect, col_base ); + + if( hover ){ + ui_outline( rect, -1, col_highlight, 0 ); + } + + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); + return 0; +} + +/* + * Tabs + * ----------------------------------------------------------------------------- + */ + +void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, + const char **titles, u32 count, i32 *page ) +{ + ui_rect bar; + ui_standard_widget( inout_panel, bar, 1 ); + + i32 cur_page = *page; + + f32 width = (f32)inout_panel[2] / (f32)count; + + ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); + inout_panel[1] = bar[1]+bar[3]; + inout_panel[3] = h; + + ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) ); + ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 ); + + rect_copy( inout_panel, out_content_panel ); + ui_rect_pad( out_content_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); + + /* place buttons */ + for( i32 i=0; ikey == ev.sym ){ + if( mapping->mod == 0 ){ + if( mod == 0 ){ + mapping->handler(); + return; + } + } + else if( (mod & mapping->mod) == mapping->mod ){ + mapping->handler(); + return; + } + } + } +} + +/* + * Callback for text entry mode + */ +void ui_proc_utf8( const char *text ) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + const char *ptr = text; + + while( *ptr ){ + if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); + ptr ++; + } + + _ui_textbox_change_callback(); + } +} + +/* + * Development utils + * ----------------------------------------------------------------------------- + */ + +void ui_dev_colourview(void) +{ + ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch; + + const char *names[vg_list_size(vg_ui.scheme)] = { + [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", + "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", + + [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", + "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", + + [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", + "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", + "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", + "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; + + ui_rect col[2]; + ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); + + for( int i=0; i 0.0001 ){" - "for( int i=0; i<4; i ++ ){" - "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" - "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" - "}" - "avg *= 0.25;" - "avg.a = 1.0;" - "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" - "}" - "else{" - "avg = aColour;" - "}" - "}" - - "FragColor = avg * uColour;" - "}" - } -}; - -static struct vg_shader _shader_ui_image = { - .name = "[vg] ui_image", - .link = NULL, - .vs = - { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - - "out vec2 aTexCoords;" - "out vec4 aColour;" - "out vec2 aWsp;" - - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.00390625;" - "aColour = a_colour;" - - "aWsp = a_co;" - "}", - }, - .fs = - { - .orig_file = NULL, - .static_src = - "uniform sampler2D uTexImage;" - "uniform vec4 uColour;" - "out vec4 FragColor;" - - "in vec2 aTexCoords;" - "in vec4 aColour;" - "in vec2 aWsp;" - - "void main()" - "{" - "vec4 colour = texture( uTexImage, aTexCoords );" - "FragColor = colour * uColour;" - "}" - } -}; - -static struct vg_shader _shader_ui_hsv = { - .name = "[vg] ui_hsv", - .link = NULL, - .vs = { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - - "out vec2 aTexCoords;" - "out vec4 aColour;" - "out vec2 aWsp;" - - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.00390625;" - "aColour = a_colour;" - - "aWsp = a_co;" - "}", - }, - .fs = - { - .orig_file = NULL, - .static_src = - "uniform float uHue;" - "out vec4 FragColor;" - - "in vec2 aTexCoords;" - "in vec4 aColour;" - "in vec2 aWsp;" - - "void main()" - "{" - "vec3 c = vec3( uHue, aTexCoords );" - "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);" - "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);" - "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);" - "FragColor = vec4( colour, 1.0 );" - "}" - } -}; - -static void _vg_ui_init(void){ - if( !vg_shader_compile( &_shader_ui ) || - !vg_shader_compile( &_shader_ui_image ) || - !vg_shader_compile( &_shader_ui_hsv ) ){ - vg_fatal_error( "Failed to compile ui shader" ); - } - - /* - * Vertex buffer - * ---------------------------------------- - */ - - vg_ui.max_indices = 20000; - vg_ui.max_verts = 30000; - - /* Generate the buffer we are gonna be drawing to */ - glGenVertexArrays( 1, &vg_ui.vao ); - glGenBuffers( 1, &vg_ui.vbo ); - glGenBuffers( 1, &vg_ui.ebo ); - - glBindVertexArray( vg_ui.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); - - glBufferData( GL_ARRAY_BUFFER, - vg_ui.max_verts * sizeof( struct ui_vert ), - NULL, GL_DYNAMIC_DRAW ); - glBindVertexArray( vg_ui.vao ); - - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); - glBufferData( GL_ELEMENT_ARRAY_BUFFER, - vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW ); - - VG_CHECK_GL_ERR(); - - /* Set pointers */ - u32 const stride = sizeof( struct ui_vert ); - - /* XY */ - glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride, - (void *)offsetof( struct ui_vert, co ) ); - glEnableVertexAttribArray( 0 ); - - /* UV */ - glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, - (void *)offsetof( struct ui_vert, uv ) ); - glEnableVertexAttribArray( 1 ); - - /* COLOUR */ - glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, - (void *)offsetof( struct ui_vert, colour ) ); - glEnableVertexAttribArray( 2 ); - - VG_CHECK_GL_ERR(); - - /* Alloc RAM default context */ - u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert), - inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) ); - - vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size ); - vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size ); - - /* font - * ----------------------------------------------------- - */ - - /* Load default font */ - u32 compressed[] = { - #include "vg/vg_pxfont_thin.h" - }; - - u32 pixels = 0, total = 256*256, data = 0; - u8 image[256*256]; - - while( pixels < total ){ - for( int b = 31; b >= 0; b-- ){ - image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u; - - if( pixels >= total ){ - total = 0; - break; - } - } - data++; - } - - glGenTextures( 1, &vg_ui.tex_glyphs ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0, - GL_RED, GL_UNSIGNED_BYTE, image ); - - VG_CHECK_GL_ERR(); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - - /* - * Cursors - * --------------------------------------------------------------- - */ - - vg_ui.cursor_map[ k_ui_cursor_default ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); - vg_ui.cursor_map[ k_ui_cursor_hand ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); - vg_ui.cursor_map[ k_ui_cursor_ibeam ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); -} - -enum ui_shader { - k_ui_shader_colour, - k_ui_shader_image, - k_ui_shader_hsv, -}; - -static void rect_copy( ui_rect a, ui_rect b ){ - for( int i=0; i<4; i++ ) - b[i] = a[i]; -} - -static void ui_flush( enum ui_shader shader, f32 w, f32 h ){ - u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert), - vertex_count = vg_ui.cur_vert-vg_ui.vert_start, - vertex_size = vertex_count*sizeof(ui_vert), - - indice_offset = vg_ui.indice_start*sizeof(u16), - indice_count = vg_ui.cur_indice-vg_ui.indice_start, - indice_size = indice_count * sizeof(u16); - - if( !vertex_size || !indice_size ) - return; - - glBindVertexArray( vg_ui.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); - glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size, - vg_ui.vertex_buffer+vg_ui.vert_start ); - - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); - glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size, - vg_ui.indice_buffer+vg_ui.indice_start ); - - glDisable( GL_DEPTH_TEST ); - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glBlendEquation( GL_FUNC_ADD ); - glDisable( GL_CULL_FACE ); - - m3x3f view = M3X3_IDENTITY; - m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } ); - m3x3_scale( view, (v3f){ 1.0f/(w*0.5f), - -1.0f/(h*0.5f), 1.0f } ); - - if( shader == k_ui_shader_colour ){ - glUseProgram( _shader_ui.id ); - - glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, - vg_ui.colour ); - - glActiveTexture( GL_TEXTURE0 ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); - glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); - - glActiveTexture( GL_TEXTURE1 ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg ); - glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); - glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), - vg_ui.frosting ); - glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), - 1, vg_ui.bg_inverse_ratio ); - } - else if( shader == k_ui_shader_image ){ - glUseProgram( _shader_ui_image.id ); - glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 ); - glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, - vg_ui.colour ); - } - else if( shader == k_ui_shader_hsv ){ - glUseProgram( _shader_ui_hsv.id ); - glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), vg_ui.hue ); - } - else - vg_fatal_error( "Invalid UI shader (%d)\n", shader ); - - glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT, - (void *)(vg_ui.indice_start*sizeof(u16)) ); - - glDisable( GL_BLEND ); - - vg_ui.indice_start = vg_ui.cur_indice; - vg_ui.vert_start = vg_ui.cur_vert; -} - -static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ){ - /* this if far from ideal but stops us from crashing */ - if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) || - (vg_ui.cur_indice + 6 > vg_ui.max_indices)) - return; - - struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; - u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; - - for( int i=0; i<4; i++ ){ - vertices[i].colour = colour; - } - - vertices[0].co[0] = rect[0]; - vertices[0].co[1] = rect[1]; - vertices[0].uv[0] = uv[0]; - vertices[0].uv[1] = uv[1]; - vertices[1].co[0] = rect[0]+rect[2]; - vertices[1].co[1] = rect[1]; - vertices[1].uv[0] = uv[2]; - vertices[1].uv[1] = uv[1]; - vertices[2].co[0] = rect[0]+rect[2]; - vertices[2].co[1] = rect[1]+rect[3]; - vertices[2].uv[0] = uv[2]; - vertices[2].uv[1] = uv[3]; - vertices[3].co[0] = rect[0]; - vertices[3].co[1] = rect[1]+rect[3]; - vertices[3].uv[0] = uv[0]; - vertices[3].uv[1] = uv[3]; - u16 ind_start = vg_ui.cur_vert; - - u16 start = vg_ui.cur_vert; - u32 mesh[] = { 0,2,1, 0,3,2 }; - - for( u32 i=0; i vg_ui.max_verts) || - (vg_ui.cur_indice + 24 > vg_ui.max_indices)) - return; - - struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; - u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; - - for( int i=0; i<8; i++ ){ - vertices[i].uv[0] = 4; - vertices[i].uv[1] = 4; - vertices[i].colour = colour; - } - - vertices[0].co[0] = rect[0]; - vertices[0].co[1] = rect[1]; - vertices[1].co[0] = rect[0]+rect[2]; - vertices[1].co[1] = rect[1]; - vertices[2].co[0] = rect[0]+rect[2]; - vertices[2].co[1] = rect[1]+rect[3]; - vertices[3].co[0] = rect[0]; - vertices[3].co[1] = rect[1]+rect[3]; - vertices[4].co[0] = vertices[0].co[0]-thickness; - vertices[4].co[1] = vertices[0].co[1]-thickness; - vertices[5].co[0] = vertices[1].co[0]+thickness; - vertices[5].co[1] = vertices[1].co[1]-thickness; - vertices[6].co[0] = vertices[2].co[0]+thickness; - vertices[6].co[1] = vertices[2].co[1]+thickness; - vertices[7].co[0] = vertices[3].co[0]-thickness; - vertices[7].co[1] = vertices[3].co[1]+thickness; - - u16 start = vg_ui.cur_vert; - u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 }; - - if( !mask ) - mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT; - - u32 c = 0; - for( u32 i=0; i rp ) dir = k_ui_axis_h; - else dir = k_ui_axis_v; - other = dir ^ 0x1; - - d[2+dir] = rect[2+dir]; - d[2+other] = (rect[2+dir] * size[other]) / size[dir]; - - ui_rect_center( rect, d ); -} - -static void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, - ui_px gap, ui_rect l, ui_rect r ){ - ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; - ui_split( rect, dir, width, gap, l, r ); -} - -static void ui_rect_pad( ui_rect rect, ui_px pad[2] ){ - rect[0] += pad[0]; - rect[1] += pad[1]; - rect[2] -= pad[0]*2; - rect[3] -= pad[1]*2; -} - -static ui_px ui_text_line_width( const char *str ){ - int length = 0; - const char *_c = str; - u8 c; - - while( (c = *(_c ++)) ){ - if( c >= 32 ) length ++; - else if( c == '\n' ) break; - } - - return length * vg_ui.font->spacing; -} - -static ui_px ui_text_string_height( const char *str ){ - int height = 1; - const char *_c = str; - u8 c; - - while( (c = *(_c ++)) ){ - if( c == '\n' ) height ++; - } - - return height * 14; -} - -static ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, - enum ui_align align ){ - enum ui_align lwr = k_ui_align_lwr & align; - if( lwr == k_ui_align_left ){ - return rect[0]; - } - else{ - ui_px width = ui_text_line_width( str ) * scale; - - if( lwr == k_ui_align_right ) - return rect[0] + rect[2]-width; - else - return rect[0] + (rect[2]-width)/2; - } -} - -static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } -static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ){ - return ui_min( max, ui_max( a, min ) ); -} - -static int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ){ - ui_px parent_max[2], child_max[2]; - parent_max[0] = parent[0]+parent[2]; - parent_max[1] = parent[1]+parent[3]; - child_max[0] = child[0]+child[2]; - child_max[1] = child[1]+child[3]; - - clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] ); - clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] ); - clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] ); - clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] ); - - if( clipped[0] == clipped[2] || - clipped[1] == clipped[3] ) - return 0; - - clipped[2] -= clipped[0]; - clipped[3] -= clipped[1]; - - return 1; -} - -static int ui_inside_rect( ui_rect rect, ui_px co[2] ){ - if( co[0] >= rect[0] && - co[1] >= rect[1] && - co[0] < rect[0]+rect[2] && - co[1] < rect[1]+rect[3] ){ - return 1; - } - else - return 0; -} - -static int ui_click_down( u32 mask ){ - if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[0] & mask) && - !(vg_ui.mouse_state[1] & mask) ) - return 1; - else - return 0; -} - -static int ui_clicking( u32 mask ){ - if( vg_ui.ignore_input_frames ) return 0; - return vg_ui.mouse_state[0] & mask; -} - -static int ui_click_up( u32 mask ){ - if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[1] & mask) && - !(vg_ui.mouse_state[0] & mask) ) - return 1; - else - return 0; -} - -static void ui_prerender(void){ - int x, y; - vg_ui.mouse_state[1] = vg_ui.mouse_state[0]; - vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y ); - vg_ui.mouse_delta[0] = x-vg_ui.mouse[0]; - vg_ui.mouse_delta[1] = y-vg_ui.mouse[1]; - vg_ui.mouse[0] = x; - vg_ui.mouse[1] = y; - - vg_ui.cur_vert = 0; - vg_ui.cur_indice = 0; - vg_ui.vert_start = 0; - vg_ui.indice_start = 0; - vg_ui.focused_control_hit = 0; - vg_ui.cursor = k_ui_cursor_default; - vg_ui.wants_mouse = 0; - - if( vg_ui.ignore_input_frames ){ - vg_ui.ignore_input_frames --; - return; - } - - if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE) ){ - vg_ui.mouse_click[0] = vg_ui.mouse[0]; - vg_ui.mouse_click[1] = vg_ui.mouse[1]; - } -} - -static u32 ui_colour( enum ui_scheme_colour id ){ - return vg_ui.scheme[ id ]; -} - -/* get an appropriately contrasting colour given the base */ -static u32 ui_colourcont( enum ui_scheme_colour id ){ - if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg ); - else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 ); - else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 ); - else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg ); - else return ui_colour( k_ui_fg+1 ); -} - -static void ui_hex_to_norm( u32 hex, v4f norm ){ - norm[0] = ((hex ) & 0xff); - norm[1] = ((hex>>8 ) & 0xff); - norm[2] = ((hex>>16) & 0xff); - norm[3] = ((hex>>24) & 0xff); - v4_muls( norm, 1.0f/255.0f, norm ); -} - -static void ui_text_glyph( const struct ui_font *font, ui_px scale, - u8 glyph, ui_rect out_texcoords ){ - glyph -= font->ascii_start; - - ui_px per_row = font->sheet_size / font->glyph_width, - column = (ui_px)glyph % per_row, - row = (glyph - column) / per_row; - - out_texcoords[0] = column * font->glyph_width; - out_texcoords[1] = row * font->glyph_height + font->offset_y; - out_texcoords[2] = out_texcoords[0] + font->glyph_width; - out_texcoords[3] = out_texcoords[1] + font->glyph_height; -} - -static u32 ui_opacity( u32 colour, f32 opacity ){ - u32 alpha = opacity * 255.0f; - return (colour & 0x00ffffff) | (alpha << 24); -} - -static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, - enum ui_align align, u32 colour ){ - ui_rect text_cursor; - if( colour == 0 ) colour = ui_colour( k_ui_fg ); - - colour &= 0x00ffffff; - - const char *_c = str; - u8 c; - - text_cursor[0] = ui_text_aligned_x( str, rect, scale, align ); - text_cursor[1] = rect[1]; - text_cursor[2] = vg_ui.font->glyph_width*scale; - text_cursor[3] = vg_ui.font->glyph_height*scale; - - u32 printed_chars = 0; - - if( align & (k_ui_align_middle|k_ui_align_bottom) ){ - ui_px height = ui_text_string_height( str ) * scale; - - if( align & k_ui_align_bottom ) - text_cursor[1] += rect[3]-height; - else - text_cursor[1] += (rect[3]-height)/2; - } - - while( (c = *(_c ++)) ){ - if( printed_chars >= len ){ - printed_chars = 0; - text_cursor[1] += vg_ui.font->line_height*scale; - text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - text_cursor[0] -= vg_ui.font->spacing*scale; - - ui_rect glyph; - ui_text_glyph( vg_ui.font, scale, '\xb6' /*FIXME*/, glyph ); - ui_fill_rect( text_cursor, 0x00ffffff, glyph ); - text_cursor[0] += vg_ui.font->spacing*scale; - } - - if( c == '\n' ){ - text_cursor[1] += vg_ui.font->line_height*scale; - text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - printed_chars = 0; - continue; - } - else if( c >= 33 ){ - ui_rect glyph; - ui_text_glyph( vg_ui.font, scale, c, glyph ); - - ui_rect cursor_clipped; - if( ui_clip( rect, text_cursor, cursor_clipped ) ){ - ui_fill_rect( cursor_clipped, colour, glyph ); - } - } - else if( c == '\x1B' ){ - /* vt codes */ - _c ++; - u16 colour_id = 0; - for( int i=0; i<3; i ++ ){ - if( _c[i] ){ - if( _c[i] == 'm' ){ - _c = _c + i + 1; - - switch( colour_id ){ - case '0': colour = ui_colour( k_ui_fg ); break; - case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break; - case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break; - case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break; - case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break; - case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break; - case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break; - case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break; - case '3'|'7'<<8: colour = 0xffffffff; break; - } - - colour &= 0x00ffffff; - break; - } - - colour_id |= _c[i] << (i*8); - } - else{ - _c = _c +i; - break; - } - } - - continue; - } - else if( c == '\t' ){ - text_cursor[0] += vg_ui.font->spacing*scale*4; - printed_chars += 4; - continue; - } - - text_cursor[0] += vg_ui.font->spacing*scale; - printed_chars ++; - } - - return printed_chars; -} - -static void ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align, u32 colour ){ - ui_ntext( rect, str, 1024, scale, align, colour ); -} - -/* - * Standard layout stuff - * ----------------------------------------------------------------------------- - */ - -static void ui_panel( ui_rect in_rect, ui_rect out_panel ){ - ui_fill( in_rect, ui_colour( k_ui_bg+1 ) ); - ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 ); - rect_copy( in_rect, out_panel ); - ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); -} - -static void ui_label( ui_rect rect, const char *text, ui_px size, - ui_px gap, ui_rect r ){ - ui_rect l; - ui_px width = (ui_text_line_width(text)+vg_ui.font->spacing) * size; - ui_split( rect, k_ui_axis_v, width, gap, l, r ); - ui_text( l, text, 1, k_ui_align_middle_left, 0 ); -} - -static void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, - ui_px count ){ - ui_px height = (count * vg_ui.font->glyph_height + 18) * k_ui_scale; - ui_split( inout_panel, k_ui_axis_h, height, k_ui_padding, - out_rect, inout_panel ); -} - -static void ui_info( ui_rect inout_panel, const char *text ){ - ui_rect box; - ui_standard_widget( inout_panel, box, 1 ); - ui_text( box, text, 1, k_ui_align_middle_left, 0 ); -} - -static void ui_image( ui_rect rect, GLuint image ){ - ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); - glActiveTexture( GL_TEXTURE0 ); - glBindTexture( GL_TEXTURE_2D, image ); - ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); - ui_flush( k_ui_shader_image, vg.window_x, vg.window_y ); -} - -static u32 v4f_u32_colour( v4f colour ){ - u32 r = colour[0] * 255.0f, - g = colour[1] * 255.0f, - b = colour[2] * 255.0f, - a = colour[3] * 255.0f; - - return r | (g<<8) | (b<<16) | (a<<24); -} - -static void ui_defocus_all(void){ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - SDL_StopTextInput(); - if( vg_ui.textbox.callbacks.escape ) - vg_ui.textbox.callbacks.escape(); - } - - vg_ui.focused_control_id = NULL; - vg_ui.focused_control_hit = 0; - vg_ui.focused_control_type = k_ui_control_none; -} +extern vg_ui; enum ui_button_state { k_ui_button_none = 0x0, @@ -1128,1224 +224,96 @@ enum ui_button_state { k_ui_button_hover = 0x8 }; -/* TODO: split this out into a formatless button and one that auto fills */ -static enum ui_button_state ui_colourbutton( ui_rect rect, - enum ui_scheme_colour colour, - enum ui_scheme_colour hover_colour, - enum ui_scheme_colour hi_colour, - bool const fill ){ - int clickup= ui_click_up(UI_MOUSE_LEFT), - click = ui_clicking(UI_MOUSE_LEFT) | clickup, - target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, - hover = ui_inside_rect( rect, vg_ui.mouse ); - - u32 col_base = vg_ui.scheme[ colour ], - col_highlight = vg_ui.scheme[ hi_colour? hi_colour: k_ui_fg ], - col_hover = vg_ui.scheme[ hover_colour? hover_colour: - colour + k_ui_brighter ]; - - if( vg_ui.focused_control_type != k_ui_control_none ){ - clickup = 0; - click = 0; - target = 0; - hover = 0; - } - - if( hover ){ - vg_ui.cursor = k_ui_cursor_hand; - } - - if( click ){ - if( target ){ - if( hover ){ - if( clickup ){ - vg_ui.ignore_input_frames = 2; - ui_defocus_all(); - - if( fill ) { - ui_fill( rect, col_highlight ); - rect_copy( rect, vg_ui.click_fader ); - rect_copy( rect, vg_ui.click_fader_end ); - vg_ui.click_fader_end[3] = 0; - ui_rect_center( rect, vg_ui.click_fader_end ); - vg_ui.click_fade_opacity = 1.0f; - } - - return k_ui_button_click; - } - else{ - if( fill ) ui_fill( rect, col_highlight ); - return k_ui_button_holding_inside; - } - } - else{ - if( fill ) ui_fill( rect, col_base ); - ui_outline( rect, 1, col_highlight, 0 ); - return k_ui_button_holding_outside; - } - } - else{ - if( fill ) ui_fill( rect, col_base ); - return k_ui_button_none; - } - } - else{ - if( hover ){ - if( fill ) ui_fill( rect, col_hover ); - return k_ui_button_hover; - } - else{ - if( fill ) ui_fill( rect, col_base ); - return k_ui_button_none; - } - } -} +/* TODO: docu.. */ + +void vg_ui_init(void); +void rect_copy( ui_rect a, ui_rect b ); +void ui_flush( enum ui_shader shader, f32 w, f32 h ); +void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ); +void ui_fill( ui_rect rect, u32 colour ); +void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ); +void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, + ui_rect l, ui_rect r ); +void ui_rect_center( ui_rect parent, ui_rect rect ); +void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ); +void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, + ui_px gap, ui_rect l, ui_rect r ); +void ui_rect_pad( ui_rect rect, ui_px pad[2] ); +ui_px ui_text_line_width( const char *str ); +ui_px ui_text_string_height( const char *str ); +ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, + enum ui_align align ); +int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ); +int ui_inside_rect( ui_rect rect, ui_px co[2] ); +int ui_click_down( u32 mask ); +int ui_clicking( u32 mask ); +int ui_click_up( u32 mask ); +void ui_prerender(void); +u32 ui_colour( enum ui_scheme_colour id ); -static enum ui_button_state ui_colourbutton_text( +/* get an appropriately contrasting colour given the base */ +u32 ui_colourcont( enum ui_scheme_colour id ); + +void ui_hex_to_norm( u32 hex, v4f norm ); +u32 v4f_u32_colour( v4f colour ); + +u32 ui_opacity( u32 colour, f32 opacity ); +u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ); +void ui_text( ui_rect rect, const char *str, ui_px scale, + enum ui_align align, u32 colour ); +void ui_panel( ui_rect in_rect, ui_rect out_panel ); +void ui_label( ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ); +void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ); +void ui_info( ui_rect inout_panel, const char *text ); +void ui_image( ui_rect rect, GLuint image ); +void ui_defocus_all(void); +enum ui_button_state ui_colourbutton( ui_rect rect, + enum ui_scheme_colour colour, + enum ui_scheme_colour hover_colour, + enum ui_scheme_colour hi_colour, + bool const fill ); +enum ui_button_state ui_colourbutton_text( ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ){ - enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0, 1 ); - ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale }; - ui_rect_center( rect, t ); - - u32 text_colour = ui_colourcont(colour); - if( state == k_ui_button_holding_inside ) - text_colour = colour; - - ui_text( t, string, scale, k_ui_align_left, text_colour ); - return state; -} - -static enum ui_button_state ui_button_text( ui_rect rect, - const char *string, ui_px scale ){ - return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 ); -} - -static enum ui_button_state ui_button( ui_rect inout_panel, - const char *string ){ - ui_rect rect; - ui_standard_widget( inout_panel, rect, 1 ); - return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 ); -} - -static void ui_enum_post(void); -static void ui_postrender(void){ - if( vg_ui.click_fade_opacity > 0.0f ){ - float scale = vg_ui.click_fade_opacity; - scale = vg_maxf( 1.0f/255.0f, scale*scale ); - - vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f; - u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; - - v4f begin, end, dest; - for( int i=0; i<4; i++ ){ - begin[i] = vg_ui.click_fader[i]; - end[i] = vg_ui.click_fader_end[i]+1; - } - - v4_lerp( end, begin, scale, dest ); - - ui_rect rect; - for( int i=0; i<4; i++ ){ - rect[i] = dest[i]; - } - - ui_fill( rect, colour ); - } - - if( vg_ui.focused_control_type == k_ui_control_enum ){ - ui_enum_post(); - } - else if( vg_ui.focused_control_type == k_ui_control_modal ){ - ui_rect screen = {0,0,vg.window_x,vg.window_y}; - ui_fill( screen, 0xa0000000 ); - ui_rect box = {0,0,400,200}; - - u32 colour = ui_colour(k_ui_fg), - type = vg_ui.modal.options & UI_MODAL_TYPE_BITS; - if ( type == 1 ) colour = ui_colour(k_ui_green); - else if( type == 2 ) colour = ui_colour(k_ui_red); - else if( type == 3 ) colour = ui_colour(k_ui_yellow); - - ui_rect_center( screen, box ); - ui_fill( box, ui_colour(k_ui_bg) ); - ui_outline( box, -1, colour, 0 ); - - ui_rect message; - rect_copy( box, message ); - message[3] = 100; - ui_rect_center( box, message ); - - ui_rect row0, row1, btn; - ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 ); - row0[0] += vg_ui.font->spacing; - ui_ntext( row0, vg_ui.modal.message, (box[2]/vg_ui.font->spacing)-2, 1, - k_ui_align_left, colour ); - - rect_copy( row1, btn ); - btn[2] = 86; - btn[3] = 28; - ui_rect_center( row1, btn ); - - vg_ui.focused_control_type = k_ui_control_none; /* HACK */ - if( ui_button_text( btn, "OK", 1 ) != 1 ) - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_modal; /* HACK */ - vg_ui.wants_mouse = 1; - } - - ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); - - if( !vg_ui.focused_control_hit ){ - ui_defocus_all(); - } - - if( vg_ui.wants_mouse ){ - SDL_SetWindowGrab( vg.window, SDL_FALSE ); - SDL_SetRelativeMouseMode( SDL_FALSE ); - } - else{ - SDL_SetWindowGrab( vg.window, SDL_TRUE ); - SDL_SetRelativeMouseMode( SDL_TRUE ); - } - - SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] ); - SDL_ShowCursor(1); -} - -/* - * checkbox - * ----------------------------------------------------------------------------- - */ - -static int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ){ - ui_rect rect, label, box; - ui_standard_widget( inout_panel, rect, 1 ); - - ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); - ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 ); - - int changed = ui_colourbutton( box, k_ui_bg, 0, 0, 1 )==1; - if( changed ) - *data = (*data) ^ 0x1; - - if( *data ){ - ui_rect_pad( box, (ui_px[2]){4,4} ); - ui_fill( box, ui_colour( k_ui_orange ) ); - } - - return changed; -} - -/* - * Dropdown / Enum - * ----------------------------------------------------------------------------- - */ - -/* - * unfortunately no return value since we only find out that event in the - * postrender step. - */ -static void ui_enum( ui_rect inout_panel, const char *str_label, - struct ui_enum_opt *options, u32 len, i32 *value ){ - ui_rect rect, label, box; - ui_standard_widget( inout_panel, rect, 1 ); - ui_label( rect, str_label, k_ui_scale, 0, box ); - - const char *display = "OUT OF RANGE"; - int valid = 0; - for( u32 i=0; iterminator to start */ - int remaining_length = strlen( vg_ui.textbuf )+1-end; - memmove( &vg_ui.textbuf[ start ], - &vg_ui.textbuf[ end ], - remaining_length ); - return start; -} - -static void _ui_textbox_to_clipboard(void){ - int start, end; - _ui_textbox_make_selection( &start, &end ); - char buffer[512]; - - if( end-start ){ - memcpy( buffer, &vg_ui.textbuf[ start ], end-start ); - buffer[ end-start ] = 0x00; - SDL_SetClipboardText( buffer ); - } -} - -static void _ui_textbox_change_callback(void){ - if( vg_ui.textbox.callbacks.change ){ - vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); - - /* we gave permission to modify the buffer in this callback so.. */ - int len = strlen( vg_ui.textbuf ); - vg_ui.textbox.cursor_user = VG_MIN( vg_ui.textbox.cursor_user, len ); - vg_ui.textbox.cursor_pos = VG_MIN( vg_ui.textbox.cursor_pos, len ); - } -} - -static void ui_start_modal( const char *message, u32 options ); -static void _ui_textbox_clipboard_paste(void){ - if( !SDL_HasClipboardText() ) - return; - - char *text = SDL_GetClipboardText(); - - if( !text ) - return; - - int datastart = _ui_textbox_delete_char( 0 ); - int length = strlen( text ); - - if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) < length ){ - ui_start_modal( "Clipboard content exceeds buffer size.", UI_MODAL_BAD ); - return; - } - - int cpylength = _ui_textbox_makeroom( datastart, length ); - - memcpy( vg_ui.textbuf + datastart, text, cpylength); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, cpylength, 1 ); - SDL_free( text ); - _ui_textbox_change_callback(); -} - -static void _ui_textbox_put_char( char c ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); - if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) <= 1 ) return; - - if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) ) - vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, 1, 1 ); -} - -/* Receed secondary cursor */ -static void _ui_textbox_left_select(void){ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 ); -} - -/* Match and receed both cursors */ -static void _ui_textbox_left(void){ - int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, -cursor_diff, 1 ); -} - -static void _ui_textbox_up(void){ - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - int line_begin = vg_ui.textbox.cursor_user; - - while( line_begin ){ - if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ - break; - } - - line_begin --; - } - - if( line_begin ){ - int line_above_begin = line_begin-1; - - while( line_above_begin ){ - if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){ - break; - } - - line_above_begin --; - } - - int offset = vg_ui.textbox.cursor_user - line_begin, - line_length_above = line_begin - line_above_begin -1; - - offset = VG_MIN( line_length_above, offset ); - - vg_ui.textbox.cursor_user = line_above_begin+offset; - vg_ui.textbox.cursor_pos = line_above_begin+offset; - } - else{ - vg_ui.textbox.cursor_user = line_begin; - vg_ui.textbox.cursor_pos = line_begin; - } - } - else{ - if( vg_ui.textbox.callbacks.up ){ - vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len ); - } - } -} - -static void _ui_textbox_down(void){ - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - int line_begin = vg_ui.textbox.cursor_user; - - while( line_begin ){ - if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ - break; - } - - line_begin --; - } - - int line_below_begin = vg_ui.textbox.cursor_user; - - while(1){ - if( vg_ui.textbuf[ line_below_begin ] == '\0' ){ - vg_ui.textbox.cursor_user = line_below_begin; - vg_ui.textbox.cursor_pos = line_below_begin; - return; - } - - if( vg_ui.textbuf[ line_below_begin ] == '\n' ){ - line_below_begin ++; - break; - } - - line_below_begin ++; - } - - int line_below_end = line_below_begin; - while(1){ - if( vg_ui.textbuf[ line_below_end ] == '\0' || - vg_ui.textbuf[ line_below_end ] == '\n' ){ - line_below_end ++; - break; - } - line_below_end ++; - } - - int offset = vg_ui.textbox.cursor_user - line_begin, - line_length_below = line_below_end - line_below_begin -1; - - offset = VG_MIN( line_length_below, offset ); - - vg_ui.textbox.cursor_user = line_below_begin+offset; - vg_ui.textbox.cursor_pos = line_below_begin+offset; - } - else{ - if( vg_ui.textbox.callbacks.down ){ - vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len ); - } - } -} - -static void _ui_textbox_right_select(void){ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 ); -} - -static void _ui_textbox_right(void){ - int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, +cursor_diff, 1 ); -} - -static void _ui_textbox_backspace(void){ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 ); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); - } -} - -static void _ui_textbox_delete(void){ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 ); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); - } -} - -static void _ui_textbox_home_select(void){ - i32 start = vg_ui.textbox.cursor_user; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - while( start ){ - if( vg_ui.textbuf[start-1] == '\n' ) - break; - else - start --; - } - } - else - start = 0; - - vg_ui.textbox.cursor_user = start; -} - -static void _ui_textbox_home(void){ - _ui_textbox_home_select(); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; -} - -static void _ui_textbox_end_select(void){ - i32 end = vg_ui.textbox.cursor_user; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - while( vg_ui.textbuf[end] ){ - if( vg_ui.textbuf[end] == '\n' ) - break; - else - end ++; - } - } - else - end = VG_MIN( vg_ui.textbox.len-1, strlen(vg_ui.textbuf) ); - - vg_ui.textbox.cursor_user = end; -} - -static void _ui_textbox_end(void){ - _ui_textbox_end_select(); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; -} - -static void _ui_textbox_select_all(void){ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0); -} - -static void _ui_textbox_cut(void){ - _ui_textbox_to_clipboard(); - vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); -} - -static void _ui_textbox_enter(void){ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.ignore_input_frames = 2; - - if( vg_ui.textbox.callbacks.enter ) - vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len ); - - if( vg_ui.focused_control_type != k_ui_control_textbox ) return; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - _ui_textbox_put_char( '\n' ); - _ui_textbox_change_callback(); - } - else{ - if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) - ui_defocus_all(); - } - } -} - -/* - * based on a visual character coordinate relative to the anchor of the textbox, - * this works out the linear place in the buffer that coordinate maps to - * - * input coordinates go in co[0], co[1], and the result index is in co[2] - */ -static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){ - int i[3] = {0,0,0}; - - char c; - while( (c = vg_ui.textbuf[i[2]]) ){ - if( i[1]==co[1] && i[0]>=co[0] ) break; - - if( i[0] >= wrap_length ){ - i[1] ++; - i[0] = 0; - } - - if( c >= 32 && c <= 126 ){ - i[0] ++; - i[2] ++; - } - else if( c == '\n' ){ - i[1] ++; - - if( i[1] > co[1] ) break; - - i[2] ++; - i[0] = 0; - } - else i[2] ++; - } - - co[0] = i[0]; - co[1] = i[1]; - co[2] = i[2]; -} - -/* - * based on the index specied in co[2], work out the visual character - * coordinates and store them in co[0], co[1] - */ -static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){ - co[0] = 0; - co[1] = 0; - - char c; - int i=0; - - while( (c = vg_ui.textbuf[i ++]) ){ - if( i > co[2] ) break; - if( co[0] >= wrap_length ){ - co[1] ++; - co[0] = 0; - } - if( c >= 32 && c <= 126 ) co[0] ++; - else if( c == '\n' ){ - co[1] ++; - co[0] = 0; - } - } -} - -/* - * calculate the number of characters remaining until either: - * - the wrap_length limit is hit - * - end of the line/string - * - * index must be fully populated with visual X/Y, and linear index - */ -static int _ui_textbox_run_remaining( int index[3], int wrap_length ){ - int i=0, printed_chars=0; - char c; - while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){ - if( index[0]+i >= wrap_length ) break; - if( c >= 32 && c <= 126 ) printed_chars ++; - else if( c == '\n' ) break; - } - - return printed_chars+1; -} - -static int ui_textbox( ui_rect inout_panel, const char *label, - char *buf, u32 len, u32 lines, u32 flags, - struct ui_textbox_callbacks *callbacks ){ - assert( lines > 0 ); - assert( buf ); - - if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; - - ui_rect rect; - ui_standard_widget( inout_panel, rect, lines ); - - if( label ) - ui_label( rect, label, 1, 0, rect ); - - int clickup= ui_click_up(UI_MOUSE_LEFT), - clickdown = ui_click_down(UI_MOUSE_LEFT), - click = ui_clicking(UI_MOUSE_LEFT) | clickup, - target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, - hover = ui_inside_rect( rect, vg_ui.mouse ); - - /* allow instant transitions from textbox->textbox */ - if( (vg_ui.focused_control_type != k_ui_control_none) && - (vg_ui.focused_control_type != k_ui_control_textbox) ){ - clickup = 0; - clickdown = 0; - click = 0; - target = 0; - hover = 0; - flags &= ~UI_TEXTBOX_AUTOFOCUS; - } - - u32 col_base = ui_colour( k_ui_bg ), - col_highlight = ui_colour( k_ui_fg ), - col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; - - ui_px border = -1; - - ui_rect text_rect; - rect_copy( rect, text_rect ); - - if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; - else text_rect[3] = vg_ui.font->line_height; - - text_rect[2] -= 16; - ui_rect_center( rect, text_rect ); - - ui_px wrap_length = 1024; - - if( flags & UI_TEXTBOX_WRAP ) - wrap_length = text_rect[2] / vg_ui.font->spacing; - - if( hover ){ - vg_ui.cursor = k_ui_cursor_ibeam; - } - - if( vg_ui.focused_control_id == buf ){ - ui_fill( rect, col_base ); - ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - - if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)){ - ui_defocus_all(); - } - else{ - vg_ui.focused_control_hit = 1; - if( click && target ){ - int p0[3] ={ - (vg_ui.mouse_click[0] - text_rect[0]) / vg_ui.font->spacing, - (vg_ui.mouse_click[1] - text_rect[1]) / vg_ui.font->line_height, - -1 - }, - p1[3] = { - (vg_ui.mouse[0] - text_rect[0]) / vg_ui.font->spacing, - (vg_ui.mouse[1] - text_rect[1]) / vg_ui.font->line_height, - -1 - }; - - if( flags & UI_TEXTBOX_MULTILINE ){ - _ui_textbox_calc_index_from_grid( p0, wrap_length ); - _ui_textbox_calc_index_from_grid( p1, wrap_length ); - - vg_ui.textbox.cursor_pos = p0[2]; - vg_ui.textbox.cursor_user = p1[2]; - } - else{ - int max = strlen( buf ); - vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), - vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); - } - } - - ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ], 0 ); - - ui_rect cursor; - - int c0 = vg_ui.textbox.cursor_pos, - c1 = vg_ui.textbox.cursor_user, - start = VG_MIN( c0, c1 ), - end = VG_MAX( c0, c1 ), - chars = end-start; - - if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ){ - int pos[3], remaining = chars; - - pos[2] = start; - _ui_textbox_index_calc_coords( pos, wrap_length ); - - if( start==end ){ - cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1; - cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = 2; - cursor[3] = 13; - ui_fill( cursor, col_cursor ); - rect_copy( cursor, vg_ui.click_fader_end ); - } - else{ - while( remaining ){ - int run = _ui_textbox_run_remaining( pos, wrap_length ); - run = VG_MIN( run, remaining ); - - cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1; - cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = (float)(run)*(float)vg_ui.font->spacing; - cursor[3] = 13; - - ui_fill( cursor, col_cursor ); - - remaining -= run; - pos[0] = 0; - pos[1] ++; - pos[2] += run; - } - rect_copy( cursor, vg_ui.click_fader_end ); - } - } - else{ - cursor[0] = text_rect[0] + start*vg_ui.font->spacing-1; - cursor[1] = text_rect[1]; - cursor[3] = 13; - - if( start==end ){ - cursor[2] = 2; - } - else{ - cursor[2] = (float)(chars)*(float)vg_ui.font->spacing; - } - - if( (vg_ui.click_fade_opacity<=0.0f) && - ui_clip( rect, cursor, cursor ) ){ - ui_fill( cursor, col_cursor ); - } - - rect_copy( cursor, vg_ui.click_fader_end ); - } - } - - return 0; - } - - if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ){ - if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ){ - ui_defocus_all(); - - ui_fill( rect, col_highlight ); - vg_ui.ignore_input_frames = 2; - rect_copy( rect, vg_ui.click_fader ); - rect_copy( rect, vg_ui.click_fader_end ); - - vg_ui.click_fade_opacity = 1.0f; - vg_ui.textbuf = buf; - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_textbox; - vg_ui.textbox.len = len; - vg_ui.textbox.flags = flags; - vg_ui.textbox.cursor_pos = 0; - vg_ui.textbox.cursor_user = 0; - - if( callbacks ){ - vg_ui.textbox.callbacks = *callbacks; - } - else{ - vg_ui.textbox.callbacks.change = NULL; - vg_ui.textbox.callbacks.down = NULL; - vg_ui.textbox.callbacks.up = NULL; - vg_ui.textbox.callbacks.enter = NULL; - } - - SDL_StartTextInput(); - } - } - - ui_fill( rect, col_base ); - - if( hover ){ - ui_outline( rect, -1, col_highlight, 0 ); - } - - ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - return 0; -} - -/* - * Tabs - * ----------------------------------------------------------------------------- - */ - -static void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, - const char **titles, u32 count, i32 *page ){ - ui_rect bar; - ui_standard_widget( inout_panel, bar, 1 ); - - i32 cur_page = *page; - - f32 width = (f32)inout_panel[2] / (f32)count; - - ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); - inout_panel[1] = bar[1]+bar[3]; - inout_panel[3] = h; - - ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) ); - ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 ); - - rect_copy( inout_panel, out_content_panel ); - ui_rect_pad( out_content_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); - - /* place buttons */ - for( i32 i=0; ikey == ev.sym ){ - if( mapping->mod == 0 ){ - if( mod == 0 ){ - mapping->handler(); - return; - } - } - else if( (mod & mapping->mod) == mapping->mod ){ - mapping->handler(); - return; - } - } - } -} - -/* - * Callback for text entry mode - */ -static void ui_proc_utf8( const char *text ){ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - const char *ptr = text; - - while( *ptr ){ - if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); - ptr ++; - } - - _ui_textbox_change_callback(); - } -} - -/* - * Development utils - * ----------------------------------------------------------------------------- - */ - -static void ui_dev_colourview(void){ - ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch; - - const char *names[vg_list_size(vg_ui.scheme)] = { - [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", - "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", - - [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", - "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", - - [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", - "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", - "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", - "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; - - ui_rect col[2]; - ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); - - for( int i=0; ihandle ){ + if( esta->instance_id == instance_id ){ + vg_warn( " . SDL_JoystickID[%d] is already in open at index #%d\n", + esta->instance_id, j ); + return -1; + } + } + else{ + if( !controller ){ + controller = &vg_input.controllers[j]; + vg_id = j; + } + } + } + + if( controller ){ + controller->handle = SDL_GameControllerOpen( index ); + controller->instance_id = instance_id; + + if( controller->handle ){ + vg_success( + " . opened SDL_JoystickID[%d] as controller '%s' at index #%d\n", + instance_id, name, vg_id ); + + for( u32 i=0; i< SDL_CONTROLLER_BUTTON_MAX; i++ ) + controller->buttons[i] = 0; + + for( u32 i=0; i< SDL_CONTROLLER_AXIS_MAX; i++ ) + controller->axises[i] = 0.0f; + + if( vg_input.active_controller_index == -2 ){ + vg_input.active_controller_index = vg_id; + vg_input.display_input_method = k_input_method_controller; + vg_input.display_input_type = + SDL_GameControllerGetType( controller->handle ); + } + + return vg_id; + } + else{ + vg_error( ". Failed to attach game controller '%s'. Reason: %s\n", + name, SDL_GetError() ); + return -1; + } + } + else{ + vg_error( ". Too many controllers open! ignoring '%s'\n", name ); + return -1; + } +} + +void vg_input_device_event( SDL_Event *ev ) +{ + if( ev->type == SDL_CONTROLLERDEVICEADDED ){ + int is_controller = SDL_IsGameController( ev->cdevice.which ); + const char *name = SDL_JoystickNameForIndex( ev->cdevice.which ); + + Sint32 index = ev->cdevice.which; + SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index ); + vg_info( "SDL_CONTROLLERDEVICEADDED | device index: %d, name: '%s'\n", + index, name ); + + if( is_controller ){ + vg_open_gamecontroller( index ); + } + } + else if( ev->type == SDL_CONTROLLERDEVICEREMOVED ){ + vg_info( "SDL_CONTROLLERDEVICEREMOVED | instance_id: %d\n", + ev->cdevice.which ); + + for( int i=0; ihandle ){ + if( controller->instance_id == ev->cdevice.which ){ + vg_info( " . closing controller at index #%d\n", i ); + SDL_GameControllerClose( controller->handle ); + controller->handle = NULL; + controller->instance_id = -1; + + if( vg_input.active_controller_index == i ){ + vg_input.active_controller_index = -1; + vg_input.display_input_method = k_input_method_kbm; + vg_info( "display_input: k_input_method_kbm\n" ); + } + break; + } + } + } + } +} + +void vg_input_controller_event( SDL_Event *ev ) +{ + if( ev->type == SDL_CONTROLLERAXISMOTION ){ + for( int i=0; icaxis.which == esta->instance_id ){ + float value = (float)ev->caxis.value / 32767.0f; + + if( ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || + ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTY || + ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX || + ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY ) + { + float deadz = vg_clampf( controller_deadzone, 0.0f, 0.999f ), + high = vg_maxf( 0.0f, fabsf(value) - deadz ); + + value = vg_signf(value) * (high / (1.0f-deadz)); + } + + esta->axises[ ev->caxis.axis ] = value; + break; + } + } + } + else if( ev->type == SDL_CONTROLLERBUTTONDOWN ){ + struct vg_controller *active = NULL; + + if( vg_input.active_controller_index >= 0 ) + active = &vg_input.controllers[vg_input.active_controller_index]; + + if( !active || (ev->cbutton.which != active->instance_id) ){ + active = NULL; + vg_input.active_controller_index = -1; + vg_input.display_input_method = k_input_method_kbm; + + for( int i=0; icbutton.which ){ + active = &vg_input.controllers[i]; + vg_input.active_controller_index = i; + vg_input.display_input_type = + SDL_GameControllerGetType(active->handle); + break; + } + } + + if( active ){ + vg_info( "Switching active controller index to #%d\n", + vg_input.active_controller_index ); + } + else{ + vg_error( "Input out of range (SDL_JoystickID#%d)\n", + ev->cbutton.which ); + } + } + + if( active ){ + if( vg_input.display_input_method != k_input_method_controller ){ + vg_input.display_input_method = k_input_method_controller; + vg_info( "display_input: k_input_method_controller\n" ); + } + active->buttons[ ev->cbutton.button ] = 1; + } + } + else if( ev->type == SDL_CONTROLLERBUTTONUP ){ + for( int i=0; icbutton.which == esta->instance_id ){ + esta->buttons[ ev->cbutton.button ] = 0; + break; + } + } + } +} + +void vg_process_inputs(void) +{ + int count; + vg_input.sdl_keys = SDL_GetKeyboardState( &count ); + vg_input.sdl_mouse = SDL_GetMouseState(NULL,NULL); + + if( vg_input.display_input_method != k_input_method_kbm ){ + /* check for giving keyboard priority */ + for( int i=0; ihandle ){ + SDL_GameControllerClose( controller->handle ); + controller->handle = NULL; + } + } +} + +struct vg_controller *vg_active_controller(void) +{ + if( vg_input.active_controller_index >= 0 ) + return &vg_input.controllers[vg_input.active_controller_index]; + else + return NULL; +} + +u8 vg_controller_button( SDL_GameControllerButton button ) +{ + struct vg_controller *c = vg_active_controller(); + if( c ) return c->buttons[ button ]; + else return 0; +} + +f32 vg_controller_axis( SDL_GameControllerAxis axis ) +{ + struct vg_controller *c = vg_active_controller(); + if( c ) return c->axises[ axis ]; + else return 0; +} + +static void vg_input_apply_to_u8( vg_input_op mode, u8 data, u8 *inout_result ){ + if ( mode == vg_mode_absmax ) *inout_result |= data; + else if( mode == vg_mode_mul ) *inout_result &= data; + else vg_fatal_error( "mode not supported for destination type (%d)", mode ); +} + +static void vg_input_apply_to_f32( vg_input_op mode, f32 data, + f32 *inout_result ){ + if ( mode == vg_mode_absmax ){ + if( fabsf(data) > fabsf(*inout_result) ) + *inout_result = data; + } + else if( mode == vg_mode_max ) *inout_result = vg_maxf(*inout_result,data); + else if( mode == vg_mode_mul ) *inout_result *= (f32)data; + else if( mode == vg_mode_sub ) *inout_result -= (f32)data; + else if( mode == vg_mode_add ) *inout_result += (f32)data; + else vg_fatal_error( "mode not supported for destination type (%d)", mode ); +} + +/* + * Run an input program. out_result must point to memory with sufficient + * storage respective to the size set by type. + */ +void vg_exec_input_program( enum vg_input_type type, vg_input_op *ops, + void *out_result ){ + u8 *out_button = NULL; + f32 *out_joy = NULL; + + if( type == k_vg_input_type_button_u8 ){ + out_button = out_result; + *out_button = 0; + } + else if( type == k_vg_input_type_axis_f32 ){ + out_joy = out_result; + out_joy[0] = 0.0f; + } + else if( type == k_vg_input_type_joy_v2f ){ + out_joy = out_result; + out_joy[0] = 0.0f; + out_joy[1] = 0.0f; + } + + /* computer state */ + vg_input_op mode = vg_mode_absmax; + u32 pc = 0, index = 0; + +next_code:; + vg_input_op op = ops[ pc ++ ]; + + if( (op >= vg_mode_mul) && (op <= vg_mode_max) ) + mode = op; + else if( (op == vg_keyboard) || (op == vg_mouse) || (op == vg_joy_button) ){ + u8 state = 0; + + if( op == vg_keyboard ) + state = vg_getkey(ops[pc ++]); + else if( op == vg_mouse ) + state = (vg_input.sdl_mouse & SDL_BUTTON(ops[pc ++]))?1:0; + else + state = vg_controller_button(ops[pc ++]); + + if( type == k_vg_input_type_button_u8 ) + vg_input_apply_to_u8( mode, state, out_button ); + else + vg_input_apply_to_f32( mode, (f32)state, &out_joy[index] ); + } + else if( op == vg_joy_axis ){ + f32 state = vg_controller_axis( ops[pc ++] ); + if( type == k_vg_input_type_button_u8 ) + vg_input_apply_to_u8( mode, state>0.5f?1:0, out_button ); + else + vg_input_apply_to_f32( mode, state, &out_joy[index] ); + } + else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){ + if( type == k_vg_input_type_joy_v2f ){ + vg_input_apply_to_f32( mode, + vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTX: + SDL_CONTROLLER_AXIS_RIGHTX), + &out_joy[0] ); + vg_input_apply_to_f32( mode, + vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTY: + SDL_CONTROLLER_AXIS_RIGHTY), + &out_joy[1] ); + } + } + else if( op == vg_index ) + index = ops[pc ++]; + else if( op == vg_end ) + return; + else if( op == vg_normalize ) + v2_normalize( out_joy ); + else if( op == vg_gui_visible ) + pc ++; + else + vg_fatal_error( "unknown op\n" ); + + goto next_code; +} + +/* + * Get vendor specific button glyphs based on SDL button ID + */ +const char *controller_button_str( SDL_GameControllerButton button ) +{ + static const char *controller_glyphs[ SDL_CONTROLLER_BUTTON_MAX ][2] = { + /* xbox/generic playstation */ + [ SDL_CONTROLLER_BUTTON_A ] = { "\x1e\x85","\x1e\x82" }, + [ SDL_CONTROLLER_BUTTON_B ] = { "\x1e\x86","\x1e\x81" }, + [ SDL_CONTROLLER_BUTTON_X ] = { "\x1e\x83","\x1e\x7f" }, + [ SDL_CONTROLLER_BUTTON_Y ] = { "\x1e\x84","\x1e\x80" }, + [ SDL_CONTROLLER_BUTTON_LEFTSTICK ] = { "\x87", "\x87" }, + [ SDL_CONTROLLER_BUTTON_RIGHTSTICK ] = { "\x8b", "\x8b" }, + [ SDL_CONTROLLER_BUTTON_LEFTSHOULDER ] = { "\x91", "\x91" }, + [ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ]= { "\x92", "\x92" }, + [ SDL_CONTROLLER_BUTTON_DPAD_LEFT ] = { "\x1e\x93","\x1e\x93" }, + [ SDL_CONTROLLER_BUTTON_DPAD_UP ] = { "\x1e\x94","\x1e\x94" }, + [ SDL_CONTROLLER_BUTTON_DPAD_RIGHT ] = { "\x1e\x95","\x1e\x95" }, + [ SDL_CONTROLLER_BUTTON_DPAD_DOWN ] = { "\x1e\x96","\x1e\x96" }, + [ SDL_CONTROLLER_BUTTON_GUIDE ] = { "\x91", "\x91" }, + }; + + if( vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS3 || + vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS4 || + vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS5 ) + { + return controller_glyphs[ button ][ 1 ]; + } + else if( vg_input.display_input_type == + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO || + vg_input.display_input_type == + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || + vg_input.display_input_type == + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR || + vg_input.display_input_type == + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT ) + { + return NULL; + } + else + return controller_glyphs[ button ][ 0 ]; +} + +/* + * Cat keyboard key string. special_glyphs include SR glyphs + */ +void vg_keyboard_key_string( vg_str *str, u32 key, int special_glyphs ) +{ + if( (key >= SDLK_a) && (key <= SDLK_z) ){ + key = (key-SDLK_a)+(u32)'A'; + + if( special_glyphs ){ + vg_strcatch( str, '\x1f' ); + vg_strcatch( str, key ); + vg_strcatch( str, ' ' ); + } + else + vg_strcatch( str, key ); + } + else if( (key == SDLK_LSHIFT) || (key == SDLK_RSHIFT) ) + vg_strcat( str, special_glyphs? "\x9e": "shift" ); + else if( (key == SDLK_LCTRL) || (key == SDLK_RCTRL) ) + vg_strcat( str, special_glyphs? "\x9f": "ctrl" ); + else if( (key == SDLK_LALT) || (key == SDLK_RALT) ) + vg_strcat( str, special_glyphs? "\xa0": "alt" ); + else if( key == SDLK_SPACE ) + vg_strcat( str, special_glyphs? "\xa1": "space" ); + else if( (key == SDLK_RETURN) || (key == SDLK_RETURN2) ) + vg_strcat( str, special_glyphs? "\xa2": "return" ); + else if( key == SDLK_ESCAPE ) + vg_strcat( str, special_glyphs? "\xa3": "escape" ); + else if( key == SDLK_RIGHT ) + vg_strcat( str, special_glyphs? "\x1f\x95 ": "right" ); + else if( key == SDLK_LEFT ) + vg_strcat( str, special_glyphs? "\x1f\x93 ": "left" ); + else if( key == SDLK_UP ) + vg_strcat( str, special_glyphs? "\x1f\x94 ": "up" ); + else if( key == SDLK_DOWN ) + vg_strcat( str, special_glyphs? "\x1f\x96 ": "down" ); + else { + vg_strcat( str, "keyboard key #" ); + vg_strcati32( str, key ); + } +} + +/* + * Cat mouse button string. special_glyphs include SR glyphs + */ +void vg_mouse_button_string( vg_str *str, u32 button, int special_glyphs ) +{ + if ( button == SDL_BUTTON_LEFT ) + vg_strcat( str, special_glyphs? "\x99": "left mouse" ); + else if( button == SDL_BUTTON_RIGHT ) + vg_strcat( str, special_glyphs? "\x9a": "right mouse" ); + else if( button == SDL_BUTTON_MIDDLE ) + vg_strcat( str, special_glyphs? "\x9c": "middle mouse" ); + else{ + vg_strcat( str, "mouse button #" ); + vg_strcati32( str, button ); + } +} + +/* + * Cat string represeinting single axis + */ +void vg_joy_axis_string( vg_str *str, SDL_GameControllerAxis axis, + int special_glyphs ) +{ + if( axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT ) + vg_strcat( str, special_glyphs?"\x8f":"left trigger" ); + else if( axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT ) + vg_strcat( str, special_glyphs?"\x90":"right trigger" ); + else if( axis == SDL_CONTROLLER_AXIS_LEFTX ) + vg_strcat( str, special_glyphs?"\x88":"left stick horizontal" ); + else if( axis == SDL_CONTROLLER_AXIS_LEFTY ) + vg_strcat( str, special_glyphs?"\x89":"left stick vertical" ); + else if( axis == SDL_CONTROLLER_AXIS_RIGHTX ) + vg_strcat( str, special_glyphs?"\x8c":"right stick horizontal" ); + else if( axis == SDL_CONTROLLER_AXIS_RIGHTY ) + vg_strcat( str, special_glyphs?"\x8d":"right stick vertical" ); + else{ + vg_strcat( str, "axis " ); + vg_strcati32( str, axis ); + } +} + +/* + * Cat string represeinting whole joystick + */ +void vg_joy_string( vg_str *str, vg_input_op op, int special_glyphs ) +{ + if( op == vg_joy_ls ) + vg_strcat( str, special_glyphs? "\x87": "left stick" ); + else + vg_strcat( str, special_glyphs? "\x8b": "right stick" ); +} + +/* + * Convert an input program into a readable string + */ +void vg_input_string( vg_str *str, vg_input_op *ops, int glyphs ) +{ + u32 pc = 0; + int applicable = 0, visible = 1; + +next_code:; + vg_input_op op = ops[ pc ++ ]; + + if( (op == vg_keyboard) || (op == vg_mouse) ){ + if( (vg_input.display_input_method == k_input_method_kbm) && visible ){ + applicable = 1; + + if( op == vg_keyboard ) + vg_keyboard_key_string( str, ops[pc], glyphs ); + else + vg_mouse_button_string( str, ops[pc], glyphs ); + } + else applicable = 0; + pc ++; + } + else if( (op == vg_joy_button) || (op == vg_joy_axis) ){ + if( (vg_input.display_input_method == k_input_method_controller) + && visible ){ + applicable = 1; + + if( op == vg_joy_button ) + vg_strcat( str, controller_button_str(ops[pc]) ); + else + vg_joy_axis_string( str, ops[pc], glyphs ); + } + else applicable = 0; + pc ++; + } + else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){ + if( (vg_input.display_input_method == k_input_method_controller) + && visible ){ + applicable = 1; + vg_joy_string( str, op, glyphs ); + } + else applicable = 0; + } + else if( op == vg_mode_mul ){ + if( applicable && visible ) + vg_strcat( str, " + " ); + } + else if( op == vg_index ) + pc ++; + else if( op == vg_gui_visible ) + visible = ops[pc++]; + else if( op == vg_end ) + return; + + goto next_code; +} diff --git a/vg_input.h b/vg_input.h index 0122c92..a203123 100644 --- a/vg_input.h +++ b/vg_input.h @@ -1,22 +1,24 @@ -/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */ -#ifndef VG_INPUT_H -#define VG_INPUT_H - -#include "vg/vg_loader.h" +#pragma once +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ #define VG_MAX_CONTROLLERS 4 -static float controller_deadzone = 0.05f; +#include "vg_platform.h" + +extern f32 controller_deadzone; + typedef u32 vg_input_op; typedef vg_input_op *vg_input_program; -enum vg_input_type { +enum vg_input_type +{ k_vg_input_type_button_u8, k_vg_input_type_axis_f32, k_vg_input_type_joy_v2f }; -enum vg_input_op { +enum vg_input_op +{ /* data source */ vg_keyboard, vg_mouse, @@ -41,7 +43,8 @@ enum vg_input_op { vg_normalize }; -struct{ +struct vg_input +{ const u8 *sdl_keys; u32 sdl_mouse; @@ -66,570 +69,24 @@ struct{ display_input_method; SDL_GameControllerType display_input_type; } -static vg_input = { .active_controller_index = -2 }; - -static u8 vg_getkey( SDL_Keycode kc ) -{ - SDL_Scancode sc = SDL_GetScancodeFromKey( kc ); - return vg_input.sdl_keys[sc]; -} - -/* - * takes SDL device index, and tries to open that on any free channel - */ -static int vg_open_gamecontroller( Sint32 index ) -{ - struct vg_controller *controller = NULL; - int vg_id = 0; - const char *name = SDL_GameControllerNameForIndex( index ); - SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index ); - - if( instance_id == -1 ){ - vg_error( ". Invalid device index (vg_open_gamecontroller)\n" ); - return -1; - } - - for( int j=0; jhandle ){ - if( esta->instance_id == instance_id ){ - vg_warn( " . SDL_JoystickID[%d] is already in open at index #%d\n", - esta->instance_id, j ); - return -1; - } - } - else{ - if( !controller ){ - controller = &vg_input.controllers[j]; - vg_id = j; - } - } - } - - if( controller ){ - controller->handle = SDL_GameControllerOpen( index ); - controller->instance_id = instance_id; - - if( controller->handle ){ - vg_success( - " . opened SDL_JoystickID[%d] as controller '%s' at index #%d\n", - instance_id, name, vg_id ); - - for( u32 i=0; i< SDL_CONTROLLER_BUTTON_MAX; i++ ) - controller->buttons[i] = 0; - - for( u32 i=0; i< SDL_CONTROLLER_AXIS_MAX; i++ ) - controller->axises[i] = 0.0f; - - if( vg_input.active_controller_index == -2 ){ - vg_input.active_controller_index = vg_id; - vg_input.display_input_method = k_input_method_controller; - vg_input.display_input_type = - SDL_GameControllerGetType( controller->handle ); - } - - return vg_id; - } - else{ - vg_error( ". Failed to attach game controller '%s'. Reason: %s\n", - name, SDL_GetError() ); - return -1; - } - } - else{ - vg_error( ". Too many controllers open! ignoring '%s'\n", name ); - return -1; - } -} - -static void vg_input_device_event( SDL_Event *ev ) -{ - if( ev->type == SDL_CONTROLLERDEVICEADDED ){ - int is_controller = SDL_IsGameController( ev->cdevice.which ); - const char *name = SDL_JoystickNameForIndex( ev->cdevice.which ); - - Sint32 index = ev->cdevice.which; - SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index ); - vg_info( "SDL_CONTROLLERDEVICEADDED | device index: %d, name: '%s'\n", - index, name ); - - if( is_controller ){ - vg_open_gamecontroller( index ); - } - } - else if( ev->type == SDL_CONTROLLERDEVICEREMOVED ){ - vg_info( "SDL_CONTROLLERDEVICEREMOVED | instance_id: %d\n", - ev->cdevice.which ); - - for( int i=0; ihandle ){ - if( controller->instance_id == ev->cdevice.which ){ - vg_info( " . closing controller at index #%d\n", i ); - SDL_GameControllerClose( controller->handle ); - controller->handle = NULL; - controller->instance_id = -1; - - if( vg_input.active_controller_index == i ){ - vg_input.active_controller_index = -1; - vg_input.display_input_method = k_input_method_kbm; - vg_info( "display_input: k_input_method_kbm\n" ); - } - break; - } - } - } - } -} - -static void vg_input_controller_event( SDL_Event *ev ) -{ - if( ev->type == SDL_CONTROLLERAXISMOTION ){ - for( int i=0; icaxis.which == esta->instance_id ){ - float value = (float)ev->caxis.value / 32767.0f; - - if( ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || - ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTY || - ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX || - ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY ) - { - float deadz = vg_clampf( controller_deadzone, 0.0f, 0.999f ), - high = vg_maxf( 0.0f, fabsf(value) - deadz ); - - value = vg_signf(value) * (high / (1.0f-deadz)); - } - - esta->axises[ ev->caxis.axis ] = value; - break; - } - } - } - else if( ev->type == SDL_CONTROLLERBUTTONDOWN ){ - struct vg_controller *active = NULL; - - if( vg_input.active_controller_index >= 0 ) - active = &vg_input.controllers[vg_input.active_controller_index]; - - if( !active || (ev->cbutton.which != active->instance_id) ){ - active = NULL; - vg_input.active_controller_index = -1; - vg_input.display_input_method = k_input_method_kbm; - - for( int i=0; icbutton.which ){ - active = &vg_input.controllers[i]; - vg_input.active_controller_index = i; - vg_input.display_input_type = - SDL_GameControllerGetType(active->handle); - break; - } - } - - if( active ){ - vg_info( "Switching active controller index to #%d\n", - vg_input.active_controller_index ); - } - else{ - vg_error( "Input out of range (SDL_JoystickID#%d)\n", - ev->cbutton.which ); - } - } - - if( active ){ - if( vg_input.display_input_method != k_input_method_controller ){ - vg_input.display_input_method = k_input_method_controller; - vg_info( "display_input: k_input_method_controller\n" ); - } - active->buttons[ ev->cbutton.button ] = 1; - } - } - else if( ev->type == SDL_CONTROLLERBUTTONUP ){ - for( int i=0; icbutton.which == esta->instance_id ){ - esta->buttons[ ev->cbutton.button ] = 0; - break; - } - } - } -} - -static void vg_process_inputs(void) -{ - int count; - vg_input.sdl_keys = SDL_GetKeyboardState( &count ); - vg_input.sdl_mouse = SDL_GetMouseState(NULL,NULL); - - if( vg_input.display_input_method != k_input_method_kbm ){ - /* check for giving keyboard priority */ - for( int i=0; ihandle ){ - SDL_GameControllerClose( controller->handle ); - controller->handle = NULL; - } - } -} - -struct vg_controller *vg_active_controller(void){ - if( vg_input.active_controller_index >= 0 ) - return &vg_input.controllers[vg_input.active_controller_index]; - else - return NULL; -} - -static u8 vg_controller_button( SDL_GameControllerButton button ){ - struct vg_controller *c = vg_active_controller(); - if( c ) return c->buttons[ button ]; - else return 0; -} - -static f32 vg_controller_axis( SDL_GameControllerAxis axis ){ - struct vg_controller *c = vg_active_controller(); - if( c ) return c->axises[ axis ]; - else return 0; -} - -static void vg_input_apply_to_u8( vg_input_op mode, u8 data, u8 *inout_result ){ - if ( mode == vg_mode_absmax ) *inout_result |= data; - else if( mode == vg_mode_mul ) *inout_result &= data; - else vg_fatal_error( "mode not supported for destination type (%d)", mode ); -} - -static void vg_input_apply_to_f32( vg_input_op mode, f32 data, - f32 *inout_result ){ - if ( mode == vg_mode_absmax ){ - if( fabsf(data) > fabsf(*inout_result) ) - *inout_result = data; - } - else if( mode == vg_mode_max ) *inout_result = vg_maxf(*inout_result,data); - else if( mode == vg_mode_mul ) *inout_result *= (f32)data; - else if( mode == vg_mode_sub ) *inout_result -= (f32)data; - else if( mode == vg_mode_add ) *inout_result += (f32)data; - else vg_fatal_error( "mode not supported for destination type (%d)", mode ); -} - -/* - * Run an input program. out_result must point to memory with sufficient - * storage respective to the size set by type. - */ -static void vg_exec_input_program( enum vg_input_type type, vg_input_op *ops, - void *out_result ){ - u8 *out_button = NULL; - f32 *out_joy = NULL; - - if( type == k_vg_input_type_button_u8 ){ - out_button = out_result; - *out_button = 0; - } - else if( type == k_vg_input_type_axis_f32 ){ - out_joy = out_result; - out_joy[0] = 0.0f; - } - else if( type == k_vg_input_type_joy_v2f ){ - out_joy = out_result; - out_joy[0] = 0.0f; - out_joy[1] = 0.0f; - } - - /* computer state */ - vg_input_op mode = vg_mode_absmax; - u32 pc = 0, index = 0; - -next_code:; - vg_input_op op = ops[ pc ++ ]; - - if( (op >= vg_mode_mul) && (op <= vg_mode_max) ) - mode = op; - else if( (op == vg_keyboard) || (op == vg_mouse) || (op == vg_joy_button) ){ - u8 state = 0; - - if( op == vg_keyboard ) - state = vg_getkey(ops[pc ++]); - else if( op == vg_mouse ) - state = (vg_input.sdl_mouse & SDL_BUTTON(ops[pc ++]))?1:0; - else - state = vg_controller_button(ops[pc ++]); - - if( type == k_vg_input_type_button_u8 ) - vg_input_apply_to_u8( mode, state, out_button ); - else - vg_input_apply_to_f32( mode, (f32)state, &out_joy[index] ); - } - else if( op == vg_joy_axis ){ - f32 state = vg_controller_axis( ops[pc ++] ); - if( type == k_vg_input_type_button_u8 ) - vg_input_apply_to_u8( mode, state>0.5f?1:0, out_button ); - else - vg_input_apply_to_f32( mode, state, &out_joy[index] ); - } - else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){ - if( type == k_vg_input_type_joy_v2f ){ - vg_input_apply_to_f32( mode, - vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTX: - SDL_CONTROLLER_AXIS_RIGHTX), - &out_joy[0] ); - vg_input_apply_to_f32( mode, - vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTY: - SDL_CONTROLLER_AXIS_RIGHTY), - &out_joy[1] ); - } - } - else if( op == vg_index ) - index = ops[pc ++]; - else if( op == vg_end ) - return; - else if( op == vg_normalize ) - v2_normalize( out_joy ); - else if( op == vg_gui_visible ) - pc ++; - else - vg_fatal_error( "unknown op\n" ); - - goto next_code; -} - -/* - * Get vendor specific button glyphs based on SDL button ID - */ -static const char *controller_button_str( SDL_GameControllerButton button ){ - static const char *controller_glyphs[ SDL_CONTROLLER_BUTTON_MAX ][2] = { - /* xbox/generic playstation */ - [ SDL_CONTROLLER_BUTTON_A ] = { "\x1e\x85","\x1e\x82" }, - [ SDL_CONTROLLER_BUTTON_B ] = { "\x1e\x86","\x1e\x81" }, - [ SDL_CONTROLLER_BUTTON_X ] = { "\x1e\x83","\x1e\x7f" }, - [ SDL_CONTROLLER_BUTTON_Y ] = { "\x1e\x84","\x1e\x80" }, - [ SDL_CONTROLLER_BUTTON_LEFTSTICK ] = { "\x87", "\x87" }, - [ SDL_CONTROLLER_BUTTON_RIGHTSTICK ] = { "\x8b", "\x8b" }, - [ SDL_CONTROLLER_BUTTON_LEFTSHOULDER ] = { "\x91", "\x91" }, - [ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ]= { "\x92", "\x92" }, - [ SDL_CONTROLLER_BUTTON_DPAD_LEFT ] = { "\x1e\x93","\x1e\x93" }, - [ SDL_CONTROLLER_BUTTON_DPAD_UP ] = { "\x1e\x94","\x1e\x94" }, - [ SDL_CONTROLLER_BUTTON_DPAD_RIGHT ] = { "\x1e\x95","\x1e\x95" }, - [ SDL_CONTROLLER_BUTTON_DPAD_DOWN ] = { "\x1e\x96","\x1e\x96" }, - [ SDL_CONTROLLER_BUTTON_GUIDE ] = { "\x91", "\x91" }, - }; - - if( vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS3 || - vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS4 || - vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS5 ) - { - return controller_glyphs[ button ][ 1 ]; - } - else if( vg_input.display_input_type == - SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO || - vg_input.display_input_type == - SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || - vg_input.display_input_type == - SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR || - vg_input.display_input_type == - SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT ) - { - return NULL; - } - else - return controller_glyphs[ button ][ 0 ]; -} - -/* - * Cat keyboard key string. special_glyphs include SR glyphs - */ -static void vg_keyboard_key_string( vg_str *str, u32 key, int special_glyphs ){ - if( (key >= SDLK_a) && (key <= SDLK_z) ){ - key = (key-SDLK_a)+(u32)'A'; - - if( special_glyphs ){ - vg_strcatch( str, '\x1f' ); - vg_strcatch( str, key ); - vg_strcatch( str, ' ' ); - } - else - vg_strcatch( str, key ); - } - else if( (key == SDLK_LSHIFT) || (key == SDLK_RSHIFT) ) - vg_strcat( str, special_glyphs? "\x9e": "shift" ); - else if( (key == SDLK_LCTRL) || (key == SDLK_RCTRL) ) - vg_strcat( str, special_glyphs? "\x9f": "ctrl" ); - else if( (key == SDLK_LALT) || (key == SDLK_RALT) ) - vg_strcat( str, special_glyphs? "\xa0": "alt" ); - else if( key == SDLK_SPACE ) - vg_strcat( str, special_glyphs? "\xa1": "space" ); - else if( (key == SDLK_RETURN) || (key == SDLK_RETURN2) ) - vg_strcat( str, special_glyphs? "\xa2": "return" ); - else if( key == SDLK_ESCAPE ) - vg_strcat( str, special_glyphs? "\xa3": "escape" ); - else if( key == SDLK_RIGHT ) - vg_strcat( str, special_glyphs? "\x1f\x95 ": "right" ); - else if( key == SDLK_LEFT ) - vg_strcat( str, special_glyphs? "\x1f\x93 ": "left" ); - else if( key == SDLK_UP ) - vg_strcat( str, special_glyphs? "\x1f\x94 ": "up" ); - else if( key == SDLK_DOWN ) - vg_strcat( str, special_glyphs? "\x1f\x96 ": "down" ); - else { - vg_strcat( str, "keyboard key #" ); - vg_strcati32( str, key ); - } -} - -/* - * Cat mouse button string. special_glyphs include SR glyphs - */ -static void vg_mouse_button_string( vg_str *str, u32 button, - int special_glyphs ){ - if ( button == SDL_BUTTON_LEFT ) - vg_strcat( str, special_glyphs? "\x99": "left mouse" ); - else if( button == SDL_BUTTON_RIGHT ) - vg_strcat( str, special_glyphs? "\x9a": "right mouse" ); - else if( button == SDL_BUTTON_MIDDLE ) - vg_strcat( str, special_glyphs? "\x9c": "middle mouse" ); - else{ - vg_strcat( str, "mouse button #" ); - vg_strcati32( str, button ); - } -} - -/* - * Cat string represeinting single axis - */ -static void vg_joy_axis_string( vg_str *str, - SDL_GameControllerAxis axis, int special_glyphs ){ - if( axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT ) - vg_strcat( str, special_glyphs?"\x8f":"left trigger" ); - else if( axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT ) - vg_strcat( str, special_glyphs?"\x90":"right trigger" ); - else if( axis == SDL_CONTROLLER_AXIS_LEFTX ) - vg_strcat( str, special_glyphs?"\x88":"left stick horizontal" ); - else if( axis == SDL_CONTROLLER_AXIS_LEFTY ) - vg_strcat( str, special_glyphs?"\x89":"left stick vertical" ); - else if( axis == SDL_CONTROLLER_AXIS_RIGHTX ) - vg_strcat( str, special_glyphs?"\x8c":"right stick horizontal" ); - else if( axis == SDL_CONTROLLER_AXIS_RIGHTY ) - vg_strcat( str, special_glyphs?"\x8d":"right stick vertical" ); - else{ - vg_strcat( str, "axis " ); - vg_strcati32( str, axis ); - } -} - -/* - * Cat string represeinting whole joystick - */ -static void vg_joy_string( vg_str *str, vg_input_op op, int special_glyphs ){ - if( op == vg_joy_ls ) - vg_strcat( str, special_glyphs? "\x87": "left stick" ); - else - vg_strcat( str, special_glyphs? "\x8b": "right stick" ); -} - -/* - * Convert an input program into a readable string - */ -static void vg_input_string( vg_str *str, vg_input_op *ops, int glyphs ){ - u32 pc = 0; - int applicable = 0, visible = 1; - -next_code:; - vg_input_op op = ops[ pc ++ ]; - - if( (op == vg_keyboard) || (op == vg_mouse) ){ - if( (vg_input.display_input_method == k_input_method_kbm) && visible ){ - applicable = 1; - - if( op == vg_keyboard ) - vg_keyboard_key_string( str, ops[pc], glyphs ); - else - vg_mouse_button_string( str, ops[pc], glyphs ); - } - else applicable = 0; - pc ++; - } - else if( (op == vg_joy_button) || (op == vg_joy_axis) ){ - if( (vg_input.display_input_method == k_input_method_controller) - && visible ){ - applicable = 1; - - if( op == vg_joy_button ) - vg_strcat( str, controller_button_str(ops[pc]) ); - else - vg_joy_axis_string( str, ops[pc], glyphs ); - } - else applicable = 0; - pc ++; - } - else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){ - if( (vg_input.display_input_method == k_input_method_controller) - && visible ){ - applicable = 1; - vg_joy_string( str, op, glyphs ); - } - else applicable = 0; - } - else if( op == vg_mode_mul ){ - if( applicable && visible ) - vg_strcat( str, " + " ); - } - else if( op == vg_index ) - pc ++; - else if( op == vg_gui_visible ) - visible = ops[pc++]; - else if( op == vg_end ) - return; - - goto next_code; -} - -#endif +extern vg_input; + +u8 vg_getkey( SDL_Keycode kc ); +void vg_process_inputs(void); +void async_vg_input_init( void *payload, u32 size ); +void vg_input_init(void); +void vg_input_free(void); +struct vg_controller *vg_active_controller(void); +u8 vg_controller_button( SDL_GameControllerButton button ); +f32 vg_controller_axis( SDL_GameControllerAxis axis ); +void vg_exec_input_program( enum vg_input_type type, vg_input_op *ops, + void *out_result ); +const char *controller_button_str( SDL_GameControllerButton button ); +void vg_keyboard_key_string( vg_str *str, u32 key, int special_glyphs ); +void vg_mouse_button_string( vg_str *str, u32 button, int special_glyphs ); +void vg_joy_axis_string( vg_str *str, SDL_GameControllerAxis axis, + int special_glyphs ); +void vg_joy_string( vg_str *str, vg_input_op op, int special_glyphs ); +void vg_input_string( vg_str *str, vg_input_op *ops, int glyphs ); +void vg_input_device_event( SDL_Event *ev ); +void vg_input_controller_event( SDL_Event *ev ); diff --git a/vg_io.c b/vg_io.c new file mode 100644 index 0000000..76e8ca3 --- /dev/null +++ b/vg_io.c @@ -0,0 +1,235 @@ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ + +#include "vg_io.h" +#include "vg_platform.h" +#include "vg_log.h" +#include "vg_mem.h" +#include +#include + +int vg_dir_open( vg_dir *dir, const char *name ) +{ +#ifdef _WIN32 + char q_buf[4096]; + vg_str q; + vg_strnull( &q, q_buf, 4096 ); + vg_strcat( &q, name ); + vg_strcat( &q, "/*" ); + if( !vg_strgood(&q) ) return 0; + + vg_info( "FindFirstFile( '%s' )\n", q.buffer ); + dir->h = FindFirstFile( q.buffer, &dir->data ); + if( dir->h == INVALID_HANDLE_VALUE ){ + if( GetLastError() == ERROR_FILE_NOT_FOUND ){ + dir->index = 0; + return 1; + } + else return 0; + } +#else + dir->h = opendir( name ); + if( !dir->h ) return 0; +#endif + dir->index = 1; + return 1; +} + +const char *vg_dir_entry_name( vg_dir *dir ) +{ +#ifdef _WIN32 + return dir->data.cFileName; +#else + return dir->data->d_name; +#endif +} + +int vg_dirskip( vg_dir *dir ) +{ + const char *s = vg_dir_entry_name(dir); +#ifdef _WIN32 + if( dir->data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) return 1; +#endif + if( s[0] == '.' ){ + if( s[1] == '\0' ) return 1; + else if( s[1] == '.' ){ + if( s[2] == '\0' ) return 1; + } + } + return 0; +} + +int vg_dir_next_entry( vg_dir *dir ) +{ +#ifdef _WIN32 + if( dir->index == 0 ) return 0; + if( dir->index > 1 ) { + dir->index ++; + if( !FindNextFile( dir->h, &dir->data ) ) return 0; + } + while( vg_dirskip(dir) ){ + dir->index ++; + if( !FindNextFile( dir->h, &dir->data ) ) return 0; + } + if( dir->index == 1 ) dir->index ++; + return 1; +#else + while( (dir->data = readdir(dir->h)) ){ + dir->index ++; + if( !vg_dirskip(dir) ) break; + } + if( dir->data ) return 1; + else return 0; +#endif +} + +enum vg_entry_type vg_dir_entry_type( vg_dir *dir ) +{ +#ifdef _WIN32 + if( dir->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + return k_vg_entry_type_dir; + return k_vg_entry_type_file; /* sketchy? */ +#else + if( dir->data->d_type == DT_DIR ) return k_vg_entry_type_dir; + if( dir->data->d_type == DT_REG ) return k_vg_entry_type_file; +#endif + return 0; +} + +void vg_dir_close( vg_dir *dir ) +{ +#ifdef _WIN32 + if( dir->index ) FindClose( dir->h ); + dir->h = INVALID_HANDLE_VALUE; +#else + closedir( dir->h ); + dir->h = NULL; + dir->data = NULL; +#endif + dir->index = 0; +} + +void vg_file_print_invalid( FILE *fp ) +{ + if( feof( fp )) { + vg_error( "mdl_open: header too short\n" ); + } + else{ + if( ferror( fp )) + vg_error( "mdl_open: %s\n", strerror(errno) ); + else + vg_error( "mdl_open: unkown failure\n" ); + + } +} + +#define VG_FILE_IO_CHUNK_SIZE 1024*256 + +/* read entire binary file */ +void *vg_file_read( void *lin_alloc, const char *path, u32 *size ) +{ + FILE *f = fopen( path, "rb" ); + if( f ){ + void *buffer = lin_alloc? vg_linear_alloc( lin_alloc, 0 ): + NULL; + u64 current = 0; + + /* read in chunks */ + for( u32 i=0; 1; i++ ){ + if( lin_alloc ) + buffer = vg_linear_extend( lin_alloc,buffer,VG_FILE_IO_CHUNK_SIZE ); + else + buffer = realloc( buffer, current + VG_FILE_IO_CHUNK_SIZE ); + + u64 l = fread( buffer + current, 1, VG_FILE_IO_CHUNK_SIZE, f ); + current += l; + + if( l != VG_FILE_IO_CHUNK_SIZE ){ + if( feof( f ) ){ + break; + } + else{ + if( ferror( f ) ){ + fclose(f); + vg_fatal_error( "read error" ); + } + else{ + fclose(f); + vg_fatal_error( "unknown error codition" ); + } + } + } + } + + if( lin_alloc ) + buffer = vg_linear_resize( lin_alloc, buffer, vg_align8(current) ); + else + buffer = realloc( buffer, vg_align8(current) ); + + fclose( f ); + + *size = (u32)current; + return buffer; + } + else{ + vg_error( "vg_disk_open_read: %s (file: %s)\n", strerror(errno), path ); + return NULL; + } +} + +/* read entire file and append a null on the end */ +char *vg_file_read_text( void *lin_alloc, const char *path, u32 *sz ) +{ + u32 size; + char *str = vg_file_read( lin_alloc, path, &size ); + + if( !str ) + return NULL; + + /* include null terminator */ + if( lin_alloc ) + str = vg_linear_extend( lin_alloc, str, 1 ); + else + str = realloc( str, size+1 ); + + str[ size ] = '\0'; + *sz = size+1; + + return str; +} + + +int vg_asset_write( const char *path, void *data, i64 size ) +{ + FILE *f = fopen( path, "wb" ); + if( f ){ + fwrite( data, size, 1, f ); + fclose( f ); + return 1; + } + else{ + return 0; + } +} + +/* TODO: error handling if read fails */ +int vg_file_copy( const char *src, const char *dst, void *lin_alloc ) +{ + vg_info( "vg_file_copy( %s -> %s )\n", src, dst ); + u32 size; + void *data = vg_file_read( lin_alloc, src, &size ); + return vg_asset_write( dst, data, size ); +} + +const char *vg_path_filename( const char *path ) +{ + const char *base = path; + + for( int i=0; i<1024; i++ ){ + if( path[i] == '\0' ) break; + if( path[i] == '/' ){ + base = path+i+1; + } + } + + return base; +} diff --git a/vg_io.h b/vg_io.h index e39c15f..fefb6bf 100644 --- a/vg_io.h +++ b/vg_io.h @@ -1,15 +1,10 @@ -/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ -#ifndef VG_IO_H -#define VG_IO_H - -#include "vg.h" -#include "vg_stdint.h" +#pragma once #include "vg_platform.h" #include "vg_log.h" #include "vg_mem.h" - typedef struct vg_dir vg_dir; #ifndef _WIN32 #include @@ -34,242 +29,24 @@ enum vg_entry_type{ k_vg_entry_type_dir }; -static int vg_dir_open( vg_dir *dir, const char *name ){ -#ifdef _WIN32 - char q_buf[4096]; - vg_str q; - vg_strnull( &q, q_buf, 4096 ); - vg_strcat( &q, name ); - vg_strcat( &q, "/*" ); - if( !vg_strgood(&q) ) return 0; - - vg_info( "FindFirstFile( '%s' )\n", q.buffer ); - dir->h = FindFirstFile( q.buffer, &dir->data ); - if( dir->h == INVALID_HANDLE_VALUE ){ - if( GetLastError() == ERROR_FILE_NOT_FOUND ){ - dir->index = 0; - return 1; - } - else return 0; - } -#else - dir->h = opendir( name ); - if( !dir->h ) return 0; -#endif - dir->index = 1; - return 1; -} - -static const char *vg_dir_entry_name( vg_dir *dir ){ -#ifdef _WIN32 - return dir->data.cFileName; -#else - return dir->data->d_name; -#endif -} - -static int vg_dirskip( vg_dir *dir ){ - const char *s = vg_dir_entry_name(dir); -#ifdef _WIN32 - if( dir->data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) return 1; -#endif - if( s[0] == '.' ){ - if( s[1] == '\0' ) return 1; - else if( s[1] == '.' ){ - if( s[2] == '\0' ) return 1; - } - } - return 0; -} - -static int vg_dir_next_entry( vg_dir *dir ){ -#ifdef _WIN32 - if( dir->index == 0 ) return 0; - if( dir->index > 1 ) { - dir->index ++; - if( !FindNextFile( dir->h, &dir->data ) ) return 0; - } - while( vg_dirskip(dir) ){ - dir->index ++; - if( !FindNextFile( dir->h, &dir->data ) ) return 0; - } - if( dir->index == 1 ) dir->index ++; - return 1; -#else - while( (dir->data = readdir(dir->h)) ){ - dir->index ++; - if( !vg_dirskip(dir) ) break; - } - if( dir->data ) return 1; - else return 0; -#endif -} - -static enum vg_entry_type vg_dir_entry_type( vg_dir *dir ){ -#ifdef _WIN32 - if( dir->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) - return k_vg_entry_type_dir; - return k_vg_entry_type_file; /* sketchy? */ -#else - if( dir->data->d_type == DT_DIR ) return k_vg_entry_type_dir; - if( dir->data->d_type == DT_REG ) return k_vg_entry_type_file; -#endif - return 0; -} - -static void vg_dir_close( vg_dir *dir ){ -#ifdef _WIN32 - if( dir->index ) FindClose( dir->h ); - dir->h = INVALID_HANDLE_VALUE; -#else - closedir( dir->h ); - dir->h = NULL; - dir->data = NULL; -#endif - dir->index = 0; -} +int vg_dir_open( vg_dir *dir, const char *name ); +const char *vg_dir_entry_name( vg_dir *dir ); +int vg_dirskip( vg_dir *dir ); +int vg_dir_next_entry( vg_dir *dir ); +enum vg_entry_type vg_dir_entry_type( vg_dir *dir ); +void vg_dir_close( vg_dir *dir ); +void vg_file_print_invalid( FILE *fp ); /* * File I/O */ -#define VG_FILE_IO_CHUNK_SIZE 1024*256 - -#ifdef __GNUC__ - #ifndef __clang__ - #pragma GCC push_options - #pragma GCC optimize ("O3") - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - #endif -#endif - -static void vg_file_print_invalid( FILE *fp ) -{ - if( feof( fp )) { - vg_error( "mdl_open: header too short\n" ); - } - else{ - if( ferror( fp )) - vg_error( "mdl_open: %s\n", strerror(errno) ); - else - vg_error( "mdl_open: unkown failure\n" ); - - } -} - /* read entire binary file */ -static void *vg_file_read( void *lin_alloc, const char *path, u32 *size ){ - FILE *f = fopen( path, "rb" ); - if( f ){ - void *buffer = lin_alloc? vg_linear_alloc( lin_alloc, 0 ): - NULL; - u64 current = 0; - - /* read in chunks */ - for( u32 i=0; 1; i++ ){ - if( lin_alloc ) - buffer = vg_linear_extend( lin_alloc,buffer,VG_FILE_IO_CHUNK_SIZE ); - else - buffer = realloc( buffer, current + VG_FILE_IO_CHUNK_SIZE ); - - u64 l = fread( buffer + current, 1, VG_FILE_IO_CHUNK_SIZE, f ); - current += l; - - if( l != VG_FILE_IO_CHUNK_SIZE ){ - if( feof( f ) ){ - break; - } - else{ - if( ferror( f ) ){ - fclose(f); - vg_fatal_error( "read error" ); - } - else{ - fclose(f); - vg_fatal_error( "unknown error codition" ); - } - } - } - } - - if( lin_alloc ) - buffer = vg_linear_resize( lin_alloc, buffer, vg_align8(current) ); - else - buffer = realloc( buffer, vg_align8(current) ); - - fclose( f ); - - *size = (u32)current; - return buffer; - } - else{ - vg_error( "vg_disk_open_read: %s (file: %s)\n", strerror(errno), path ); - return NULL; - } -} +void *vg_file_read( void *lin_alloc, const char *path, u32 *size ); /* read entire file and append a null on the end */ -static char *vg_file_read_text( void *lin_alloc, const char *path, u32 *sz ){ - u32 size; - char *str = vg_file_read( lin_alloc, path, &size ); - - if( !str ) - return NULL; - - /* include null terminator */ - if( lin_alloc ) - str = vg_linear_extend( lin_alloc, str, 1 ); - else - str = realloc( str, size+1 ); - - str[ size ] = '\0'; - *sz = size+1; - - return str; -} - - -static int vg_asset_write( const char *path, void *data, i64 size ){ - FILE *f = fopen( path, "wb" ); - if( f ){ - fwrite( data, size, 1, f ); - fclose( f ); - return 1; - } - else{ - return 0; - } -} - -/* TODO: error handling if read fails */ -static int vg_file_copy( const char *src, const char *dst, void *lin_alloc ) -{ - vg_info( "vg_file_copy( %s -> %s )\n", src, dst ); - u32 size; - void *data = vg_file_read( lin_alloc, src, &size ); - return vg_asset_write( dst, data, size ); -} - -static const char *vg_path_filename( const char *path ) -{ - const char *base = path; - - for( int i=0; i<1024; i++ ){ - if( path[i] == '\0' ) break; - if( path[i] == '/' ){ - base = path+i+1; - } - } - - return base; -} - -#ifdef __GNUC__ - #ifndef __clang__ - #pragma GCC pop_options - #pragma GCC diagnostic pop - #endif -#endif +char *vg_file_read_text( void *lin_alloc, const char *path, u32 *sz ); -#endif /* VG_IO_H */ +int vg_asset_write( const char *path, void *data, i64 size ); +int vg_file_copy( const char *src, const char *dst, void *lin_alloc ); +const char *vg_path_filename( const char *path ); diff --git a/vg_lines.c b/vg_lines.c new file mode 100644 index 0000000..27694fc --- /dev/null +++ b/vg_lines.c @@ -0,0 +1,356 @@ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ + +#pragma once +#include "vg_lines.h" +#include "vg_shader.h" +#include "vg_engine.h" +#include "vg_async.h" + +struct vg_lines vg_lines; + +/* + * FIXME: The line buffer sometimes overflows. Low priority + */ + +static struct vg_shader _shader_lines = { + .name = "[vg] lines", + .link = NULL, + .vs = { + .orig_file = NULL, + .static_src = + + "uniform mat4 uPv;" + "layout (location=0) in vec3 a_co;" + "layout (location=1) in vec4 a_colour;" + "" + "out vec4 s_colour;" + "" + "void main()" + "{" + " vec4 vert_pos = uPv * vec4( a_co, 1.0 );" + " s_colour = a_colour;" + " gl_Position = vert_pos;" + "}" + }, + .fs = { + .orig_file = NULL, + .static_src = + + "out vec4 FragColor;" + "" + "in vec4 s_colour;" + "" + "void main()" + "{" + " FragColor = s_colour;" + "}" + } +}; + +#define VG_LINES_BUFFER_SIZE 50000 * sizeof( struct vg_lines_vert ) + +static void async_vg_lines_init( void *payload, u32 payload_size ) +{ + glGenVertexArrays( 1, &vg_lines.vao ); + glGenBuffers( 1, &vg_lines.vbo ); + glBindVertexArray( vg_lines.vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_lines.vbo ); + + glBufferData( GL_ARRAY_BUFFER, VG_LINES_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW ); + glBindVertexArray( vg_lines.vao ); + VG_CHECK_GL_ERR(); + + /* Pointers */ + glVertexAttribPointer( + 0, + 3, + GL_FLOAT, + GL_FALSE, + sizeof( struct vg_lines_vert ), + (void *)0 + ); + glEnableVertexAttribArray( 0 ); + + glVertexAttribPointer( + 1, + 4, + GL_UNSIGNED_BYTE, + GL_TRUE, + sizeof( struct vg_lines_vert ), + (void*)(offsetof( struct vg_lines_vert, colour )) + ); + glEnableVertexAttribArray( 1 ); + + VG_CHECK_GL_ERR(); +} + +void vg_lines_init(void) +{ + vg_lines.vertex_buffer = + vg_create_linear_allocator( vg_mem.rtmemory, + VG_LINES_BUFFER_SIZE, VG_MEMORY_REALTIME); + + vg_async_call( async_vg_lines_init, NULL, 0 ); + + vg_console_reg_var( "vg_lines", &vg_lines.render, k_var_dtype_i32, + VG_VAR_CHEAT ); + vg_shader_register( &_shader_lines ); +} + +void vg_lines_drawall( void ) +{ + glUseProgram( _shader_lines.id ); + + glUniformMatrix4fv( glGetUniformLocation( _shader_lines.id, "uPv" ), + 1, GL_FALSE, (float *)vg.pv ); + + glBindVertexArray( vg_lines.vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_lines.vbo ); + + u32 bufusage = vg_linear_get_cur(vg_lines.vertex_buffer); + glBufferSubData( GL_ARRAY_BUFFER, 0, bufusage, vg_lines.vertex_buffer ); + + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glBlendEquation( GL_FUNC_ADD ); + + if( vg_lines.render ) + glDrawArrays( GL_LINES, 0, bufusage / sizeof(struct vg_lines_vert) ); + + glDisable( GL_BLEND ); + vg_linear_clear( vg_lines.vertex_buffer ); +} + +void vg_line2( line_co from, line_co to, u32 fc, u32 tc ) +{ + if( !vg_lines.enabled ) return; + + u32 size = 2 * sizeof(struct vg_lines_vert); + struct vg_lines_vert *v = vg_linear_alloc( vg_lines.vertex_buffer, size ); + + v3_copy( from, v[0].co ); + v3_copy( to, v[1].co ); + + v[0].colour = fc; + v[1].colour = tc; +} + +void vg_line( line_co from, line_co to, u32 colour ) +{ + if( !vg_lines.enabled ) return; + + vg_line2( from, to, colour, colour ); +} + +void vg_line_arrow( line_co co, line_co dir, float size, u32 colour ) +{ + if( !vg_lines.enabled ) return; + + v3f p1, tx, ty, p2, p3; + v3_muladds( co, dir, size, p1 ); + v3_tangent_basis( dir, tx, ty ); + + v3_muladds( p1, dir, -size * 0.125f, p2 ); + v3_muladds( p2, ty, size * 0.125f, p3 ); + v3_muladds( p2, ty, -size * 0.125f, p2 ); + + vg_line( co, p1, colour ); + vg_line( p1, p2, colour ); + vg_line( p1, p3, colour ); +} + +void vg_line_box_verts( boxf box, v3f verts[8] ) +{ + if( !vg_lines.enabled ) return; + + for( u32 i=0; i<8; i++ ){ + for( u32 j=0; j<3; j++ ){ + verts[i][j] = i&(0x1< %p\n", step->fn_free ); + step->fn_free(); + } +} + +void vg_loader_render_ring( f32 opacity ) +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + + opacity *= opacity; + + glUseProgram( _shader_loader.id ); + glUniform1f( glGetUniformLocation( _shader_loader.id, "uTime" ), vg.time ); + f32 ratio = (f32)vg.window_x / (f32)vg.window_y; + glUniform1f( glGetUniformLocation( _shader_loader.id, "uRatio"), ratio ); + glUniform1f( glGetUniformLocation( _shader_loader.id, "uOpacity"), opacity ); + glBindVertexArray( vg_loader.vao ); + glDrawArrays( GL_TRIANGLES, 0, 6 ); +} + +void vg_loader_render(void) +{ + glViewport( 0,0, vg.window_x, vg.window_y ); + glBindFramebuffer( GL_FRAMEBUFFER, 0 ); + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + vg.loader_ring = 1.0f; +} + +static int _vg_loader_thread( void *pfn ){ + if( setjmp( vg.env_loader_exit ) ) + return 0; + + /* Run client loader */ + //vg_info( "Starting client loader thread @%p\n", pfn ); + void (*call_func)(void *data) = pfn; + call_func( vg.thread_data ); + + SDL_SemPost( vg.sem_loader ); + vg.thread_id_loader = 0; + + return 0; +} + +int vg_loader_availible(void) +{ + if( SDL_SemValue( vg.sem_loader ) ){ + if( !(vg_async.start) ) + return 1; + } + + return 0; +} + +void vg_loader_start( void(*pfn)(void *data), void *data ) +{ + SDL_SemWait( vg.sem_loader ); + + vg.thread_data = data; + SDL_CreateThread( _vg_loader_thread, "vg: loader", pfn ); +} + +/* + * Schedule something to be ran now, freed later. Checks in with engine status + */ +void _vg_loader_step( void( *fn_load )(void), void( *fn_free )(void), + const char *alias ){ + + u64 t0 = SDL_GetPerformanceCounter(); + vg.time_hp_last = vg.time_hp; + + if( fn_load ) + fn_load(); + + u64 udt = SDL_GetPerformanceCounter() - t0; + double dt = (double)udt / (double)SDL_GetPerformanceFrequency(); + vg_info( "ltime [%p] %s: %fs\n", fn_load, alias, dt ); + + if( fn_free ){ + struct loader_free_step step; + step.fn_free = fn_free; + + if( vg_loader.step_count == vg_list_size(vg_loader.step_buffer) ) + vg_fatal_error( "Too many free steps" ); + + vg_loader.step_buffer[ vg_loader.step_count ++ ] = step; + } + + /* TODO: There was a quit checker here, re-add this? */ +} diff --git a/vg_loader.h b/vg_loader.h index 5d6d81a..ec8fb0d 100644 --- a/vg_loader.h +++ b/vg_loader.h @@ -1,5 +1,6 @@ + /* - * Copyright 2021-2022 (C) Mount0 Software, Harry Godden - All Rights Reserved + * Copyright 2021-2024 (C) Mount0 Software, Harry Godden - All Rights Reserved * ----------------------------------------------------------------------------- * * Splash / load screen @@ -7,77 +8,9 @@ * ----------------------------------------------------------------------------- */ -#ifndef VG_LOADER_H -#define VG_LOADER_H - -#define VG_GAME -#include "vg/vg.h" -#include "vg/vg_shader.h" - -static void vg_loader_start( void(*pfn)(void *data), void *data ); -static void vg_loader_step( void( *fn_load )(void), void( *fn_free )(void) ); - -static struct vg_shader _shader_loader = -{ - .name = "[vg] loader", - .link = NULL, - - /* This is the new foreground shader */ - .vs = - { - .orig_file = NULL, - .static_src = "" - "layout (location=0) in vec2 a_co;" - "out vec2 aUv;" - "void main()" - "{" - "gl_Position = vec4(a_co*2.0-1.0,0.0,1.0);" - "aUv = a_co;" - "}" - }, - .fs = - { - .orig_file = NULL, - .static_src = - - "out vec4 FragColor;" - "uniform float uTime;" - "uniform float uRatio;" - "uniform float uOpacity;" - "in vec2 aUv;" - - "float eval_zero( vec2 uv )" - "{" - "vec4 vsines = sin( (uTime+uv.y*80.0) * vec4(1.1,2.0234,3.73,2.444) );" - "float gradient = min( uv.y, 0.0 );" - "float offset = vsines.x*vsines.y*vsines.z*vsines.w*gradient;" - - "vec2 vpos = uv + vec2( offset, 0.0 );" - "float dist = dot( vpos, vpos );" - - "float fring = step(0.1*0.1,dist) * step(dist,0.15*0.15);" - "return max( 0.0, fring * 1.0+gradient*6.0 );" - "}" - - "void main()" - "{" - "vec3 col = 0.5+0.5*sin( uTime + aUv.xyx + vec3(0.0,2.0,4.0) );" - - "vec2 uvx = aUv - vec2( 0.5 );" - "uvx.x *= uRatio;" - "uvx.y *= 0.75;" +#pragma once - "float zero = eval_zero( uvx );" - - "float dither=fract(dot(vec2(171.0,231.0),gl_FragCoord.xy)/71.0)-0.5;" - "float fmt1 = step( 0.5, zero*zero + dither )*0.8+0.2;" - - "FragColor = vec4(vec3(fmt1),uOpacity);" - "}" - } -}; - -static struct vg_loader +struct vg_loader { /* Shutdown steps */ struct loader_free_step{ @@ -88,171 +21,16 @@ static struct vg_loader GLuint vao, vbo; } -vg_loader; - -static void _vg_loader_init(void) -{ - float quad[] = { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; - - glGenVertexArrays( 1, &vg_loader.vao ); - glGenBuffers( 1, &vg_loader.vbo ); - glBindVertexArray( vg_loader.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_loader.vbo ); - glBufferData( GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW ); - glBindVertexArray( vg_loader.vao ); - glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, sizeof(float)*2, (void*)0 ); - glEnableVertexAttribArray( 0 ); - - VG_CHECK_GL_ERR(); - - if( !vg_shader_compile( &_shader_loader ) ) - vg_fatal_error( "failed to compile shader" ); -} - -static void _vg_loader_free(void) -{ - vg_info( "vg_loader_free\n" ); - glDeleteVertexArrays( 1, &vg_loader.vao ); - glDeleteBuffers( 1, &vg_loader.vbo ); - - for( int i=0; i %p\n", step->fn_free ); - step->fn_free(); - } -} - -static void _vg_render_log(void) -{ -#if 0 - ui_begin( vg.window_x, vg.window_y ); - SDL_AtomicLock( &log_print_sl ); - - int const fh = 14; - int lines_screen_max = ((vg.window_y/fh)-2), - lines_max_draw = VG_MIN( lines_screen_max, vg_list_size(vg_log.buffer) ), - lines_to_draw = VG_MIN( lines_max_draw, vg_log.buffer_line_count ); - - int ptr = vg_log.buffer_line_current; - - vg_uictx.cursor[0] = 0; - vg_uictx.cursor[1] = lines_to_draw*fh; - vg_uictx.cursor[3] = fh; - ui_fill_x(); - - for( int i=0; i #include #include -#include "vg_stdint.h" #include "vg_platform.h" #include "vg_log.h" +#include "vg_string.h" #ifndef _WIN32 #include #endif -static void _vg_log_append_line( const char *str ){ +struct vg_log vg_log; + +static void _vg_log_append_line( const char *str ) +{ if( vg_log.log_line_count < vg_list_size( vg_log.log ) ) vg_log.log_line_count ++; @@ -21,7 +23,7 @@ static void _vg_log_append_line( const char *str ){ vg_log.log_line_current = 0; } -static void _vg_logx_va( FILE *file, +void _vg_logx_va( FILE *file, const char *location, const char *prefix, const char *colour, const char *fmt, va_list args ) @@ -32,8 +34,8 @@ static void _vg_logx_va( FILE *file, * | dwajdkiawjdiw */ -#ifdef VG_GAME - SDL_AtomicLock( &log_print_sl ); +#ifdef VG_ENGINE + SDL_AtomicLock( &vg_log.print_sl ); #endif char buffer[4096]; @@ -62,7 +64,7 @@ static void _vg_logx_va( FILE *file, _vg_log_append_line( logline ); if( location ){ -#ifdef VG_GAME +#ifdef VG_ENGINE const char *thread_colours[] = { KGRN, KMAG, KCYN, KYEL, KBLU }; @@ -87,15 +89,16 @@ static void _vg_logx_va( FILE *file, } } -#ifdef VG_GAME - SDL_AtomicUnlock( &log_print_sl ); +#ifdef VG_ENGINE + SDL_AtomicUnlock( &vg_log.print_sl ); #endif } -static void vg_logx( FILE *file, - const char *location, const char *prefix, - const char *colour, - const char *fmt, ... ){ +void vg_logx( FILE *file, + const char *location, const char *prefix, + const char *colour, + const char *fmt, ... ) +{ va_list args; va_start( args, fmt ); @@ -109,9 +112,9 @@ static void vg_logx( FILE *file, va_end( args ); } -static void vg_print_backtrace(void){ +void vg_print_backtrace(void) +{ #ifndef _WIN32 - void *array[20]; char **strings; int size, i; @@ -119,7 +122,8 @@ static void vg_print_backtrace(void){ size = backtrace( array, 20 ); strings = backtrace_symbols( array, size ); - if( strings != NULL ){ + if( strings != NULL ) + { vg_error( "---------------- gnu backtrace -------------\n" ); for( int i=0; i -#include "vg_stdint.h" #define VG_LOG_MCSTR(S) VG_LOG_MCSTR2(S) #define VG_LOG_MCSTR2(S) #S @@ -40,28 +39,30 @@ #define PRINTF_U64 "%lu" #endif -#ifdef VG_GAME +#ifdef VG_ENGINE #include "dep/sdl/include/SDL.h" - static SDL_SpinLock log_print_sl; #endif -struct vg_log{ +struct vg_log +{ char log[64][96]; u32 log_line_count, log_line_current; -} -static vg_log; -static void vg_logx( FILE *file, - const char *location, const char *prefix, - const char *colour, - const char *fmt, ... ); +#ifdef VG_ENGINE + SDL_SpinLock print_sl; +#endif +} +extern vg_log; -static void _vg_logx_va( FILE *file, - const char *location, const char *prefix, - const char *colour, - const char *fmt, va_list args ); +void vg_logx( FILE *file, + const char *location, const char *prefix, + const char *colour, + const char *fmt, ... ); -static void vg_print_backtrace(void); +void _vg_logx_va( FILE *file, + const char *location, const char *prefix, + const char *colour, + const char *fmt, va_list args ); +void vg_print_backtrace(void); -#endif diff --git a/vg_m.h b/vg_m.h index 3d4f53f..4af60c8 100644 --- a/vg_m.h +++ b/vg_m.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved * * 0. Misc * 1. Scalar operations @@ -24,8 +24,7 @@ * 6.a Random numbers */ -#ifndef VG_M_H -#define VG_M_H +#pragma once #include "vg_platform.h" #include @@ -1644,7 +1643,7 @@ static int plane_intersect3( v4f a, v4f b, v4f c, v3f p ) return 1; } -int plane_intersect2( v4f a, v4f b, v3f p, v3f n ) +static int plane_intersect2( v4f a, v4f b, v3f p, v3f n ) { f32 const epsilon = 1e-6f; @@ -2050,7 +2049,7 @@ static void closest_point_elipse( v2f p, v2f e, v2f o ) * ----------------------------------------------------------------------------- */ -int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist ) +static int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist ) { v3f v0, v1; f32 tmin, tmax; @@ -2610,5 +2609,3 @@ static void vg_rgb_hsv( v3f rgb, v3f hsv ){ hsv[0] = vg_fractf( hsv[0] * (60.0f/360.0f) ); } - -#endif /* VG_M_H */ diff --git a/vg_m.hc b/vg_m.hc new file mode 100644 index 0000000..bdf5343 --- /dev/null +++ b/vg_m.hc @@ -0,0 +1,2611 @@ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved + * + * 0. Misc + * 1. Scalar operations + * 2. Vectors + * 2.a 2D Vectors + * 2.b 3D Vectors + * 2.c 4D Vectors + * 3. Quaternions + * 4. Matrices + * 4.a 2x2 matrices + * 4.b 3x3 matrices + * 4.c 4x3 matrices + * 4.d 4x4 matrices + * 5. Geometry + * 5.a Boxes + * 5.b Planes + * 5.c Closest points + * 5.d Raycast & Spherecasts + * 5.e Curves + * 5.f Volumes + * 5.g Inertia tensors + * 6. Statistics + * 6.a Random numbers + */ + +#pragma once + +#include "vg_stdint.h" +#include +#include + +#define VG_PIf 3.14159265358979323846264338327950288f +#define VG_TAUf 6.28318530717958647692528676655900576f + +/* + * ----------------------------------------------------------------------------- + * Section 0. Misc Operations + * ----------------------------------------------------------------------------- + */ + +/* get the f32 as the raw bits in a u32 without converting */ +static u32 vg_ftu32( f32 a ) +{ + u32 *ptr = (u32 *)(&a); + return *ptr; +} + +/* check if f32 is infinite */ +static int vg_isinff( f32 a ) +{ + return ((vg_ftu32(a)) & 0x7FFFFFFFU) == 0x7F800000U; +} + +/* check if f32 is not a number */ +static int vg_isnanf( f32 a ) +{ + return !vg_isinff(a) && ((vg_ftu32(a)) & 0x7F800000U) == 0x7F800000U; +} + +/* check if f32 is a number and is not infinite */ +static int vg_validf( f32 a ) +{ + return ((vg_ftu32(a)) & 0x7F800000U) != 0x7F800000U; +} + +static int v3_valid( v3f a ){ + for( u32 i=0; i<3; i++ ) + if( !vg_validf(a[i]) ) return 0; + return 1; +} + +/* + * ----------------------------------------------------------------------------- + * Section 1. Scalar Operations + * ----------------------------------------------------------------------------- + */ + +static inline f32 vg_minf( f32 a, f32 b ){ return a < b? a: b; } +static inline f32 vg_maxf( f32 a, f32 b ){ return a > b? a: b; } + +static inline int vg_min( int a, int b ){ return a < b? a: b; } +static inline int vg_max( int a, int b ){ return a > b? a: b; } + +static inline f32 vg_clampf( f32 a, f32 min, f32 max ) +{ + return vg_minf( max, vg_maxf( a, min ) ); +} + +static inline f32 vg_signf( f32 a ) +{ + return a < 0.0f? -1.0f: 1.0f; +} + +static inline f32 vg_fractf( f32 a ) +{ + return a - floorf( a ); +} + +static inline f64 vg_fractf64( f64 a ){ + return a - floor( a ); +} + +static f32 vg_cfrictf( f32 velocity, f32 F ) +{ + return -vg_signf(velocity) * vg_minf( F, fabsf(velocity) ); +} + +static inline f32 vg_rad( f32 deg ) +{ + return deg * VG_PIf / 180.0f; +} + +/* angle to reach b from a */ +static f32 vg_angle_diff( f32 a, f32 b ){ + f32 d = fmod(b,VG_TAUf)-fmodf(a,VG_TAUf); + if( fabsf(d) > VG_PIf ) + d = -vg_signf(d) * (VG_TAUf - fabsf(d)); + + return d; +} + +/* + * quantize float to bit count + */ +static u32 vg_quantf( f32 a, u32 bits, f32 min, f32 max ){ + u32 mask = (0x1 << bits) - 1; + return vg_clampf((a - min) * ((f32)mask/(max-min)), 0.0f, mask ); +} + +/* + * un-quantize discreet to float + */ +static f32 vg_dequantf( u32 q, u32 bits, f32 min, f32 max ){ + u32 mask = (0x1 << bits) - 1; + return min + (f32)q * ((max-min) / (f32)mask); +} + +/* https://iquilezles.org/articles/functions/ + * + * Use k to control the stretching of the function. Its maximum, which is 1, + * happens at exactly x = 1/k. + */ +static f32 vg_exp_impulse( f32 x, f32 k ){ + f32 h = k*x; + return h*expf(1.0f-h); +} + +/* + * ----------------------------------------------------------------------------- + * Section 2.a 2D Vectors + * ----------------------------------------------------------------------------- + */ + +static inline void v2_copy( v2f a, v2f d ) +{ + d[0] = a[0]; d[1] = a[1]; +} + +static inline void v2_zero( v2f a ) +{ + a[0] = 0.f; a[1] = 0.f; +} + +static inline void v2_add( v2f a, v2f b, v2f d ) +{ + d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; +} + +static inline void v2_sub( v2f a, v2f b, v2f d ) +{ + d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; +} + +static inline void v2_minv( v2f a, v2f b, v2f dest ) +{ + dest[0] = vg_minf(a[0], b[0]); + dest[1] = vg_minf(a[1], b[1]); +} + +static inline void v2_maxv( v2f a, v2f b, v2f dest ) +{ + dest[0] = vg_maxf(a[0], b[0]); + dest[1] = vg_maxf(a[1], b[1]); +} + +static inline f32 v2_dot( v2f a, v2f b ) +{ + return a[0] * b[0] + a[1] * b[1]; +} + +static inline f32 v2_cross( v2f a, v2f b ) +{ + return a[0]*b[1] - a[1]*b[0]; +} + +static inline void v2_abs( v2f a, v2f d ) +{ + d[0] = fabsf( a[0] ); + d[1] = fabsf( a[1] ); +} + +static inline void v2_muls( v2f a, f32 s, v2f d ) +{ + d[0] = a[0]*s; d[1] = a[1]*s; +} + +static inline void v2_divs( v2f a, f32 s, v2f d ) +{ + d[0] = a[0]/s; d[1] = a[1]/s; +} + +static inline void v2_mul( v2f a, v2f b, v2f d ) +{ + d[0] = a[0]*b[0]; + d[1] = a[1]*b[1]; +} + +static inline void v2_div( v2f a, v2f b, v2f d ) +{ + d[0] = a[0]/b[0]; d[1] = a[1]/b[1]; +} + +static inline void v2_muladd( v2f a, v2f b, v2f s, v2f d ) +{ + d[0] = a[0]+b[0]*s[0]; + d[1] = a[1]+b[1]*s[1]; +} + +static inline void v2_muladds( v2f a, v2f b, f32 s, v2f d ) +{ + d[0] = a[0]+b[0]*s; + d[1] = a[1]+b[1]*s; +} + +static inline f32 v2_length2( v2f a ) +{ + return a[0]*a[0] + a[1]*a[1]; +} + +static inline f32 v2_length( v2f a ) +{ + return sqrtf( v2_length2( a ) ); +} + +static inline f32 v2_dist2( v2f a, v2f b ) +{ + v2f delta; + v2_sub( a, b, delta ); + return v2_length2( delta ); +} + +static inline f32 v2_dist( v2f a, v2f b ) +{ + return sqrtf( v2_dist2( a, b ) ); +} + +static inline void v2_lerp( v2f a, v2f b, f32 t, v2f d ) +{ + d[0] = a[0] + t*(b[0]-a[0]); + d[1] = a[1] + t*(b[1]-a[1]); +} + +static inline void v2_normalize( v2f a ) +{ + v2_muls( a, 1.0f / v2_length( a ), a ); +} + +static void v2_normalize_clamp( v2f a ) +{ + f32 l2 = v2_length2( a ); + if( l2 > 1.0f ) + v2_muls( a, 1.0f/sqrtf(l2), a ); +} + +static inline void v2_floor( v2f a, v2f b ) +{ + b[0] = floorf( a[0] ); + b[1] = floorf( a[1] ); +} + +static inline void v2_fill( v2f a, f32 v ) +{ + a[0] = v; + a[1] = v; +} + +static inline void v2_copysign( v2f a, v2f b ) +{ + a[0] = copysignf( a[0], b[0] ); + a[1] = copysignf( a[1], b[1] ); +} + +/* integer variants + * ---------------- */ + +static inline void v2i_copy( v2i a, v2i b ) +{ + b[0] = a[0]; b[1] = a[1]; +} + +static inline int v2i_eq( v2i a, v2i b ) +{ + return ((a[0] == b[0]) && (a[1] == b[1])); +} + +static inline void v2i_add( v2i a, v2i b, v2i d ) +{ + d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; +} + +static inline void v2i_sub( v2i a, v2i b, v2i d ) +{ + d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; +} + +/* + * ----------------------------------------------------------------------------- + * Section 2.b 3D Vectors + * ----------------------------------------------------------------------------- + */ + +static inline void v3_copy( v3f a, v3f b ) +{ + b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; +} + +static inline void v3_zero( v3f a ) +{ + a[0] = 0.f; a[1] = 0.f; a[2] = 0.f; +} + +static inline void v3_add( v3f a, v3f b, v3f d ) +{ + d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; d[2] = a[2]+b[2]; +} + +static inline void v3i_add( v3i a, v3i b, v3i d ) +{ + d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; d[2] = a[2]+b[2]; +} + +static inline void v3_sub( v3f a, v3f b, v3f d ) +{ + d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; d[2] = a[2]-b[2]; +} + +static inline void v3i_sub( v3i a, v3i b, v3i d ) +{ + d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; d[2] = a[2]-b[2]; +} + +static inline void v3_mul( v3f a, v3f b, v3f d ) +{ + d[0] = a[0]*b[0]; d[1] = a[1]*b[1]; d[2] = a[2]*b[2]; +} + +static inline void v3_div( v3f a, v3f b, v3f d ) +{ + d[0] = b[0]!=0.0f? a[0]/b[0]: INFINITY; + d[1] = b[1]!=0.0f? a[1]/b[1]: INFINITY; + d[2] = b[2]!=0.0f? a[2]/b[2]: INFINITY; +} + +static inline void v3_muls( v3f a, f32 s, v3f d ) +{ + d[0] = a[0]*s; d[1] = a[1]*s; d[2] = a[2]*s; +} + +static inline void v3_fill( v3f a, f32 v ) +{ + a[0] = v; + a[1] = v; + a[2] = v; +} + +static inline void v3_divs( v3f a, f32 s, v3f d ) +{ + if( s == 0.0f ) + v3_fill( d, INFINITY ); + else + { + d[0] = a[0]/s; + d[1] = a[1]/s; + d[2] = a[2]/s; + } +} + +static inline void v3_muladds( v3f a, v3f b, f32 s, v3f d ) +{ + d[0] = a[0]+b[0]*s; d[1] = a[1]+b[1]*s; d[2] = a[2]+b[2]*s; +} + +static inline void v3_muladd( v2f a, v2f b, v2f s, v2f d ) +{ + d[0] = a[0]+b[0]*s[0]; + d[1] = a[1]+b[1]*s[1]; + d[2] = a[2]+b[2]*s[2]; +} + +static inline f32 v3_dot( v3f a, v3f b ) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +static inline void v3_cross( v3f a, v3f b, v3f dest ) +{ + v3f d; + d[0] = a[1]*b[2] - a[2]*b[1]; + d[1] = a[2]*b[0] - a[0]*b[2]; + d[2] = a[0]*b[1] - a[1]*b[0]; + v3_copy( d, dest ); +} + +static inline f32 v3_length2( v3f a ) +{ + return v3_dot( a, a ); +} + +static inline f32 v3_length( v3f a ) +{ + return sqrtf( v3_length2( a ) ); +} + +static inline f32 v3_dist2( v3f a, v3f b ) +{ + v3f delta; + v3_sub( a, b, delta ); + return v3_length2( delta ); +} + +static inline f32 v3_dist( v3f a, v3f b ) +{ + return sqrtf( v3_dist2( a, b ) ); +} + +static inline void v3_normalize( v3f a ) +{ + v3_muls( a, 1.f / v3_length( a ), a ); +} + +static inline f32 vg_lerpf( f32 a, f32 b, f32 t ){ + return a + t*(b-a); +} + +static inline f64 vg_lerp( f64 a, f64 b, f64 t ) +{ + return a + t*(b-a); +} + +static inline void vg_slewf( f32 *a, f32 b, f32 speed ){ + f32 d = vg_signf( b-*a ), + c = *a + d*speed; + *a = vg_minf( b*d, c*d ) * d; +} + +static inline f32 vg_smoothstepf( f32 x ){ + return x*x*(3.0f - 2.0f*x); +} + + +/* correctly lerp around circular period -pi -> pi */ +static f32 vg_alerpf( f32 a, f32 b, f32 t ) +{ + f32 d = fmodf( b-a, VG_TAUf ), + s = fmodf( 2.0f*d, VG_TAUf ) - d; + return a + s*t; +} + +static inline void v3_lerp( v3f a, v3f b, f32 t, v3f d ) +{ + d[0] = a[0] + t*(b[0]-a[0]); + d[1] = a[1] + t*(b[1]-a[1]); + d[2] = a[2] + t*(b[2]-a[2]); +} + +static inline void v3_minv( v3f a, v3f b, v3f dest ) +{ + dest[0] = vg_minf(a[0], b[0]); + dest[1] = vg_minf(a[1], b[1]); + dest[2] = vg_minf(a[2], b[2]); +} + +static inline void v3_maxv( v3f a, v3f b, v3f dest ) +{ + dest[0] = vg_maxf(a[0], b[0]); + dest[1] = vg_maxf(a[1], b[1]); + dest[2] = vg_maxf(a[2], b[2]); +} + +static inline f32 v3_minf( v3f a ) +{ + return vg_minf( vg_minf( a[0], a[1] ), a[2] ); +} + +static inline f32 v3_maxf( v3f a ) +{ + return vg_maxf( vg_maxf( a[0], a[1] ), a[2] ); +} + +static inline void v3_floor( v3f a, v3f b ) +{ + b[0] = floorf( a[0] ); + b[1] = floorf( a[1] ); + b[2] = floorf( a[2] ); +} + +static inline void v3_ceil( v3f a, v3f b ) +{ + b[0] = ceilf( a[0] ); + b[1] = ceilf( a[1] ); + b[2] = ceilf( a[2] ); +} + +static inline void v3_negate( v3f a, v3f b ) +{ + b[0] = -a[0]; + b[1] = -a[1]; + b[2] = -a[2]; +} + +static inline void v3_rotate( v3f v, f32 angle, v3f axis, v3f d ) +{ + v3f v1, v2, k; + f32 c, s; + + c = cosf( angle ); + s = sinf( angle ); + + v3_copy( axis, k ); + v3_normalize( k ); + v3_muls( v, c, v1 ); + v3_cross( k, v, v2 ); + v3_muls( v2, s, v2 ); + v3_add( v1, v2, v1 ); + v3_muls( k, v3_dot(k, v) * (1.0f - c), v2); + v3_add( v1, v2, d ); +} + +static void v3_tangent_basis( v3f n, v3f tx, v3f ty ){ + /* Compute tangent basis (box2d) */ + if( fabsf( n[0] ) >= 0.57735027f ){ + tx[0] = n[1]; + tx[1] = -n[0]; + tx[2] = 0.0f; + } + else{ + tx[0] = 0.0f; + tx[1] = n[2]; + tx[2] = -n[1]; + } + + v3_normalize( tx ); + v3_cross( n, tx, ty ); +} + +/* + * Compute yaw and pitch based of a normalized vector representing forward + * forward: -z + * result -> (YAW,PITCH,0.0) + */ +static void v3_angles( v3f v, v3f out_angles ){ + float yaw = atan2f( v[0], -v[2] ), + pitch = atan2f( + -v[1], + sqrtf( + v[0]*v[0] + v[2]*v[2] + ) + ); + + out_angles[0] = yaw; + out_angles[1] = pitch; + out_angles[2] = 0.0f; +} + +/* + * Compute the forward vector from (YAW,PITCH,ROLL) + * forward: -z + */ +static void v3_angles_vector( v3f angles, v3f out_v ){ + out_v[0] = sinf( angles[0] ) * cosf( angles[1] ); + out_v[1] = -sinf( angles[1] ); + out_v[2] = -cosf( angles[0] ) * cosf( angles[1] ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 2.c 4D Vectors + * ----------------------------------------------------------------------------- + */ + +static inline void v4_copy( v4f a, v4f b ) +{ + b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; b[3] = a[3]; +} + +static inline void v4_add( v4f a, v4f b, v4f d ) +{ + d[0] = a[0]+b[0]; + d[1] = a[1]+b[1]; + d[2] = a[2]+b[2]; + d[3] = a[3]+b[3]; +} + +static inline void v4_zero( v4f a ) +{ + a[0] = 0.f; a[1] = 0.f; a[2] = 0.f; a[3] = 0.f; +} + +static inline void v4_muls( v4f a, f32 s, v4f d ) +{ + d[0] = a[0]*s; + d[1] = a[1]*s; + d[2] = a[2]*s; + d[3] = a[3]*s; +} + +static inline void v4_muladds( v4f a, v4f b, f32 s, v4f d ) +{ + d[0] = a[0]+b[0]*s; + d[1] = a[1]+b[1]*s; + d[2] = a[2]+b[2]*s; + d[3] = a[3]+b[3]*s; +} + +static inline void v4_lerp( v4f a, v4f b, f32 t, v4f d ) +{ + d[0] = a[0] + t*(b[0]-a[0]); + d[1] = a[1] + t*(b[1]-a[1]); + d[2] = a[2] + t*(b[2]-a[2]); + d[3] = a[3] + t*(b[3]-a[3]); +} + +static inline f32 v4_dot( v4f a, v4f b ) +{ + return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; +} + +static inline f32 v4_length( v4f a ) +{ + return sqrtf( v4_dot(a,a) ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 3 Quaternions + * ----------------------------------------------------------------------------- + */ + +static inline void q_identity( v4f q ) +{ + q[0] = 0.0f; q[1] = 0.0f; q[2] = 0.0f; q[3] = 1.0f; +} + +static inline void q_axis_angle( v4f q, v3f axis, f32 angle ) +{ + f32 a = angle*0.5f, + c = cosf(a), + s = sinf(a); + + q[0] = s*axis[0]; + q[1] = s*axis[1]; + q[2] = s*axis[2]; + q[3] = c; +} + +static inline void q_mul( v4f q, v4f q1, v4f d ) +{ + v4f t; + t[0] = q[3]*q1[0] + q[0]*q1[3] + q[1]*q1[2] - q[2]*q1[1]; + t[1] = q[3]*q1[1] - q[0]*q1[2] + q[1]*q1[3] + q[2]*q1[0]; + t[2] = q[3]*q1[2] + q[0]*q1[1] - q[1]*q1[0] + q[2]*q1[3]; + t[3] = q[3]*q1[3] - q[0]*q1[0] - q[1]*q1[1] - q[2]*q1[2]; + v4_copy( t, d ); +} + +static inline void q_normalize( v4f q ) +{ + f32 l2 = v4_dot(q,q); + if( l2 < 0.00001f ) q_identity( q ); + else { + f32 s = 1.0f/sqrtf(l2); + q[0] *= s; + q[1] *= s; + q[2] *= s; + q[3] *= s; + } +} + +static inline void q_inv( v4f q, v4f d ) +{ + f32 s = 1.0f / v4_dot(q,q); + d[0] = -q[0]*s; + d[1] = -q[1]*s; + d[2] = -q[2]*s; + d[3] = q[3]*s; +} + +static inline void q_nlerp( v4f a, v4f b, f32 t, v4f d ){ + if( v4_dot(a,b) < 0.0f ){ + v4f c; + v4_muls( b, -1.0f, c ); + v4_lerp( a, c, t, d ); + } + else + v4_lerp( a, b, t, d ); + + q_normalize( d ); +} + +static inline void q_m3x3( v4f q, m3x3f d ) +{ + f32 + l = v4_length(q), + s = l > 0.0f? 2.0f/l: 0.0f, + + xx = s*q[0]*q[0], xy = s*q[0]*q[1], wx = s*q[3]*q[0], + yy = s*q[1]*q[1], yz = s*q[1]*q[2], wy = s*q[3]*q[1], + zz = s*q[2]*q[2], xz = s*q[0]*q[2], wz = s*q[3]*q[2]; + + d[0][0] = 1.0f - yy - zz; + d[1][1] = 1.0f - xx - zz; + d[2][2] = 1.0f - xx - yy; + d[0][1] = xy + wz; + d[1][2] = yz + wx; + d[2][0] = xz + wy; + d[1][0] = xy - wz; + d[2][1] = yz - wx; + d[0][2] = xz - wy; +} + +static void q_mulv( v4f q, v3f v, v3f d ) +{ + v3f v1, v2; + + v3_muls( q, 2.0f*v3_dot(q,v), v1 ); + v3_muls( v, q[3]*q[3] - v3_dot(q,q), v2 ); + v3_add( v1, v2, v1 ); + v3_cross( q, v, v2 ); + v3_muls( v2, 2.0f*q[3], v2 ); + v3_add( v1, v2, d ); +} + +static f32 q_dist( v4f q0, v4f q1 ){ + return acosf( 2.0f * v4_dot(q0,q1) -1.0f ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 4.a 2x2 matrices + * ----------------------------------------------------------------------------- + */ + +#define M2X2_INDENTIY {{1.0f, 0.0f, }, \ + {0.0f, 1.0f, }} + +#define M2X2_ZERO {{0.0f, 0.0f, }, \ + {0.0f, 0.0f, }} + +static inline void m2x2_copy( m2x2f a, m2x2f b ) +{ + v2_copy( a[0], b[0] ); + v2_copy( a[1], b[1] ); +} + +static inline void m2x2_identity( m2x2f a ) +{ + m2x2f id = M2X2_INDENTIY; + m2x2_copy( id, a ); +} + +static inline void m2x2_create_rotation( m2x2f a, f32 theta ) +{ + f32 s, c; + + s = sinf( theta ); + c = cosf( theta ); + + a[0][0] = c; + a[0][1] = -s; + a[1][0] = s; + a[1][1] = c; +} + +static inline void m2x2_mulv( m2x2f m, v2f v, v2f d ) +{ + v2f res; + + res[0] = m[0][0]*v[0] + m[1][0]*v[1]; + res[1] = m[0][1]*v[0] + m[1][1]*v[1]; + + v2_copy( res, d ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 4.b 3x3 matrices + * ----------------------------------------------------------------------------- + */ + +#define M3X3_IDENTITY {{1.0f, 0.0f, 0.0f, },\ + { 0.0f, 1.0f, 0.0f, },\ + { 0.0f, 0.0f, 1.0f, }} + +#define M3X3_ZERO {{0.0f, 0.0f, 0.0f, },\ + { 0.0f, 0.0f, 0.0f, },\ + { 0.0f, 0.0f, 0.0f, }} + + +static void euler_m3x3( v3f angles, m3x3f d ) +{ + f32 cosY = cosf( angles[0] ), + sinY = sinf( angles[0] ), + cosP = cosf( angles[1] ), + sinP = sinf( angles[1] ), + cosR = cosf( angles[2] ), + sinR = sinf( angles[2] ); + + d[2][0] = -sinY * cosP; + d[2][1] = sinP; + d[2][2] = cosY * cosP; + + d[0][0] = cosY * cosR; + d[0][1] = sinR; + d[0][2] = sinY * cosR; + + v3_cross( d[0], d[2], d[1] ); +} + +static void m3x3_q( m3x3f m, v4f q ) +{ + f32 diag, r, rinv; + + diag = m[0][0] + m[1][1] + m[2][2]; + if( diag >= 0.0f ) + { + r = sqrtf( 1.0f + diag ); + rinv = 0.5f / r; + q[0] = rinv * (m[1][2] - m[2][1]); + q[1] = rinv * (m[2][0] - m[0][2]); + q[2] = rinv * (m[0][1] - m[1][0]); + q[3] = r * 0.5f; + } + else if( m[0][0] >= m[1][1] && m[0][0] >= m[2][2] ) + { + r = sqrtf( 1.0f - m[1][1] - m[2][2] + m[0][0] ); + rinv = 0.5f / r; + q[0] = r * 0.5f; + q[1] = rinv * (m[0][1] + m[1][0]); + q[2] = rinv * (m[0][2] + m[2][0]); + q[3] = rinv * (m[1][2] - m[2][1]); + } + else if( m[1][1] >= m[2][2] ) + { + r = sqrtf( 1.0f - m[0][0] - m[2][2] + m[1][1] ); + rinv = 0.5f / r; + q[0] = rinv * (m[0][1] + m[1][0]); + q[1] = r * 0.5f; + q[2] = rinv * (m[1][2] + m[2][1]); + q[3] = rinv * (m[2][0] - m[0][2]); + } + else + { + r = sqrtf( 1.0f - m[0][0] - m[1][1] + m[2][2] ); + rinv = 0.5f / r; + q[0] = rinv * (m[0][2] + m[2][0]); + q[1] = rinv * (m[1][2] + m[2][1]); + q[2] = r * 0.5f; + q[3] = rinv * (m[0][1] - m[1][0]); + } +} + +/* a X b == [b]T a == ...*/ +static void m3x3_skew_symetric( m3x3f a, v3f v ) +{ + a[0][0] = 0.0f; + a[0][1] = v[2]; + a[0][2] = -v[1]; + a[1][0] = -v[2]; + a[1][1] = 0.0f; + a[1][2] = v[0]; + a[2][0] = v[1]; + a[2][1] = -v[0]; + a[2][2] = 0.0f; +} + +/* aka kronecker product */ +static void m3x3_outer_product( m3x3f out_m, v3f a, v3f b ) +{ + out_m[0][0] = a[0]*b[0]; + out_m[0][1] = a[0]*b[1]; + out_m[0][2] = a[0]*b[2]; + out_m[1][0] = a[1]*b[0]; + out_m[1][1] = a[1]*b[1]; + out_m[1][2] = a[1]*b[2]; + out_m[2][0] = a[2]*b[0]; + out_m[2][1] = a[2]*b[1]; + out_m[2][2] = a[2]*b[2]; +} + +static void m3x3_add( m3x3f a, m3x3f b, m3x3f d ) +{ + v3_add( a[0], b[0], d[0] ); + v3_add( a[1], b[1], d[1] ); + v3_add( a[2], b[2], d[2] ); +} + +static void m3x3_sub( m3x3f a, m3x3f b, m3x3f d ) +{ + v3_sub( a[0], b[0], d[0] ); + v3_sub( a[1], b[1], d[1] ); + v3_sub( a[2], b[2], d[2] ); +} + +static inline void m3x3_copy( m3x3f a, m3x3f b ) +{ + v3_copy( a[0], b[0] ); + v3_copy( a[1], b[1] ); + v3_copy( a[2], b[2] ); +} + +static inline void m3x3_identity( m3x3f a ) +{ + m3x3f id = M3X3_IDENTITY; + m3x3_copy( id, a ); +} + +static void m3x3_diagonal( m3x3f out_a, f32 v ) +{ + m3x3_identity( out_a ); + out_a[0][0] = v; + out_a[1][1] = v; + out_a[2][2] = v; +} + +static void m3x3_setdiagonalv3( m3x3f a, v3f v ) +{ + a[0][0] = v[0]; + a[1][1] = v[1]; + a[2][2] = v[2]; +} + +static inline void m3x3_zero( m3x3f a ) +{ + m3x3f z = M3X3_ZERO; + m3x3_copy( z, a ); +} + +static inline void m3x3_inv( m3x3f src, m3x3f dest ) +{ + f32 a = src[0][0], b = src[0][1], c = src[0][2], + d = src[1][0], e = src[1][1], f = src[1][2], + g = src[2][0], h = src[2][1], i = src[2][2]; + + f32 det = 1.f / + (+a*(e*i-h*f) + -b*(d*i-f*g) + +c*(d*h-e*g)); + + dest[0][0] = (e*i-h*f)*det; + dest[0][1] = -(b*i-c*h)*det; + dest[0][2] = (b*f-c*e)*det; + dest[1][0] = -(d*i-f*g)*det; + dest[1][1] = (a*i-c*g)*det; + dest[1][2] = -(a*f-d*c)*det; + dest[2][0] = (d*h-g*e)*det; + dest[2][1] = -(a*h-g*b)*det; + dest[2][2] = (a*e-d*b)*det; +} + +static f32 m3x3_det( m3x3f m ) +{ + return m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) + - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); +} + +static inline void m3x3_transpose( m3x3f src, m3x3f dest ) +{ + f32 a = src[0][0], b = src[0][1], c = src[0][2], + d = src[1][0], e = src[1][1], f = src[1][2], + g = src[2][0], h = src[2][1], i = src[2][2]; + + dest[0][0] = a; + dest[0][1] = d; + dest[0][2] = g; + dest[1][0] = b; + dest[1][1] = e; + dest[1][2] = h; + dest[2][0] = c; + dest[2][1] = f; + dest[2][2] = i; +} + +static inline void m3x3_mul( m3x3f a, m3x3f b, m3x3f d ) +{ + f32 a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], + a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], + a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], + + b00 = b[0][0], b01 = b[0][1], b02 = b[0][2], + b10 = b[1][0], b11 = b[1][1], b12 = b[1][2], + b20 = b[2][0], b21 = b[2][1], b22 = b[2][2]; + + d[0][0] = a00*b00 + a10*b01 + a20*b02; + d[0][1] = a01*b00 + a11*b01 + a21*b02; + d[0][2] = a02*b00 + a12*b01 + a22*b02; + d[1][0] = a00*b10 + a10*b11 + a20*b12; + d[1][1] = a01*b10 + a11*b11 + a21*b12; + d[1][2] = a02*b10 + a12*b11 + a22*b12; + d[2][0] = a00*b20 + a10*b21 + a20*b22; + d[2][1] = a01*b20 + a11*b21 + a21*b22; + d[2][2] = a02*b20 + a12*b21 + a22*b22; +} + +static inline void m3x3_mulv( m3x3f m, v3f v, v3f d ) +{ + v3f res; + + res[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2]; + res[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2]; + res[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2]; + + v3_copy( res, d ); +} + +static inline void m3x3_projection( m3x3f dst, + f32 const left, f32 const right, f32 const bottom, f32 const top ) +{ + f32 rl, tb; + + m3x3_zero( dst ); + + rl = 1.0f / (right - left); + tb = 1.0f / (top - bottom); + + dst[0][0] = 2.0f * rl; + dst[1][1] = 2.0f * tb; + dst[2][2] = 1.0f; +} + +static inline void m3x3_translate( m3x3f m, v3f v ) +{ + m[2][0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0]; + m[2][1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1]; + m[2][2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2]; +} + +static inline void m3x3_scale( m3x3f m, v3f v ) +{ + v3_muls( m[0], v[0], m[0] ); + v3_muls( m[1], v[1], m[1] ); + v3_muls( m[2], v[2], m[2] ); +} + +static inline void m3x3_scalef( m3x3f m, f32 f ) +{ + v3f v; + v3_fill( v, f ); + m3x3_scale( m, v ); +} + +static inline void m3x3_rotate( m3x3f m, f32 angle ) +{ + f32 m00 = m[0][0], m10 = m[1][0], + m01 = m[0][1], m11 = m[1][1], + m02 = m[0][2], m12 = m[1][2]; + f32 c, s; + + s = sinf( angle ); + c = cosf( angle ); + + m[0][0] = m00 * c + m10 * s; + m[0][1] = m01 * c + m11 * s; + m[0][2] = m02 * c + m12 * s; + + m[1][0] = m00 * -s + m10 * c; + m[1][1] = m01 * -s + m11 * c; + m[1][2] = m02 * -s + m12 * c; +} + +/* + * ----------------------------------------------------------------------------- + * Section 4.c 4x3 matrices + * ----------------------------------------------------------------------------- + */ + +#define M4X3_IDENTITY {{1.0f, 0.0f, 0.0f, },\ + { 0.0f, 1.0f, 0.0f, },\ + { 0.0f, 0.0f, 1.0f, },\ + { 0.0f, 0.0f, 0.0f }} + +static inline void m4x3_to_3x3( m4x3f a, m3x3f b ) +{ + v3_copy( a[0], b[0] ); + v3_copy( a[1], b[1] ); + v3_copy( a[2], b[2] ); +} + +static inline void m4x3_invert_affine( m4x3f a, m4x3f b ) +{ + m3x3_transpose( a, b ); + m3x3_mulv( b, a[3], b[3] ); + v3_negate( b[3], b[3] ); +} + +static void m4x3_invert_full( m4x3f src, m4x3f dst ) +{ + f32 t2, t4, t5, + det, + a = src[0][0], b = src[0][1], c = src[0][2], + e = src[1][0], f = src[1][1], g = src[1][2], + i = src[2][0], j = src[2][1], k = src[2][2], + m = src[3][0], n = src[3][1], o = src[3][2]; + + t2 = j*o - n*k; + t4 = i*o - m*k; + t5 = i*n - m*j; + + dst[0][0] = f*k - g*j; + dst[1][0] =-(e*k - g*i); + dst[2][0] = e*j - f*i; + dst[3][0] =-(e*t2 - f*t4 + g*t5); + + dst[0][1] =-(b*k - c*j); + dst[1][1] = a*k - c*i; + dst[2][1] =-(a*j - b*i); + dst[3][1] = a*t2 - b*t4 + c*t5; + + t2 = f*o - n*g; + t4 = e*o - m*g; + t5 = e*n - m*f; + + dst[0][2] = b*g - c*f ; + dst[1][2] =-(a*g - c*e ); + dst[2][2] = a*f - b*e ; + dst[3][2] =-(a*t2 - b*t4 + c * t5); + + det = 1.0f / (a * dst[0][0] + b * dst[1][0] + c * dst[2][0]); + v3_muls( dst[0], det, dst[0] ); + v3_muls( dst[1], det, dst[1] ); + v3_muls( dst[2], det, dst[2] ); + v3_muls( dst[3], det, dst[3] ); +} + +static inline void m4x3_copy( m4x3f a, m4x3f b ) +{ + v3_copy( a[0], b[0] ); + v3_copy( a[1], b[1] ); + v3_copy( a[2], b[2] ); + v3_copy( a[3], b[3] ); +} + +static inline void m4x3_identity( m4x3f a ) +{ + m4x3f id = M4X3_IDENTITY; + m4x3_copy( id, a ); +} + +static void m4x3_mul( m4x3f a, m4x3f b, m4x3f d ) +{ + f32 + a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], + a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], + a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], + a30 = a[3][0], a31 = a[3][1], a32 = a[3][2], + b00 = b[0][0], b01 = b[0][1], b02 = b[0][2], + b10 = b[1][0], b11 = b[1][1], b12 = b[1][2], + b20 = b[2][0], b21 = b[2][1], b22 = b[2][2], + b30 = b[3][0], b31 = b[3][1], b32 = b[3][2]; + + d[0][0] = a00*b00 + a10*b01 + a20*b02; + d[0][1] = a01*b00 + a11*b01 + a21*b02; + d[0][2] = a02*b00 + a12*b01 + a22*b02; + d[1][0] = a00*b10 + a10*b11 + a20*b12; + d[1][1] = a01*b10 + a11*b11 + a21*b12; + d[1][2] = a02*b10 + a12*b11 + a22*b12; + d[2][0] = a00*b20 + a10*b21 + a20*b22; + d[2][1] = a01*b20 + a11*b21 + a21*b22; + d[2][2] = a02*b20 + a12*b21 + a22*b22; + d[3][0] = a00*b30 + a10*b31 + a20*b32 + a30; + d[3][1] = a01*b30 + a11*b31 + a21*b32 + a31; + d[3][2] = a02*b30 + a12*b31 + a22*b32 + a32; +} + +#if 0 /* shat appf mingw wstringop-overflow */ +inline +#endif +static void m4x3_mulv( m4x3f m, v3f v, v3f d ) +{ + v3f res; + + res[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]; + res[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]; + res[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]; + + v3_copy( res, d ); +} + +/* + * Transform plane ( xyz, distance ) + */ +static void m4x3_mulp( m4x3f m, v4f p, v4f d ) +{ + v3f o; + + v3_muls( p, p[3], o ); + m4x3_mulv( m, o, o ); + m3x3_mulv( m, p, d ); + + d[3] = v3_dot( o, d ); +} + +/* + * Affine transforms + */ + +static void m4x3_translate( m4x3f m, v3f v ) +{ + v3_muladds( m[3], m[0], v[0], m[3] ); + v3_muladds( m[3], m[1], v[1], m[3] ); + v3_muladds( m[3], m[2], v[2], m[3] ); +} + +static void m4x3_rotate_x( m4x3f m, f32 angle ) +{ + m4x3f t = M4X3_IDENTITY; + f32 c, s; + + c = cosf( angle ); + s = sinf( angle ); + + t[1][1] = c; + t[1][2] = s; + t[2][1] = -s; + t[2][2] = c; + + m4x3_mul( m, t, m ); +} + +static void m4x3_rotate_y( m4x3f m, f32 angle ) +{ + m4x3f t = M4X3_IDENTITY; + f32 c, s; + + c = cosf( angle ); + s = sinf( angle ); + + t[0][0] = c; + t[0][2] = -s; + t[2][0] = s; + t[2][2] = c; + + m4x3_mul( m, t, m ); +} + +static void m4x3_rotate_z( m4x3f m, f32 angle ) +{ + m4x3f t = M4X3_IDENTITY; + f32 c, s; + + c = cosf( angle ); + s = sinf( angle ); + + t[0][0] = c; + t[0][1] = s; + t[1][0] = -s; + t[1][1] = c; + + m4x3_mul( m, t, m ); +} + +static void m4x3_expand( m4x3f m, m4x4f d ) +{ + v3_copy( m[0], d[0] ); + v3_copy( m[1], d[1] ); + v3_copy( m[2], d[2] ); + v3_copy( m[3], d[3] ); + d[0][3] = 0.0f; + d[1][3] = 0.0f; + d[2][3] = 0.0f; + d[3][3] = 1.0f; +} + +static void m4x3_decompose( m4x3f m, v3f co, v4f q, v3f s ) +{ + v3_copy( m[3], co ); + s[0] = v3_length(m[0]); + s[1] = v3_length(m[1]); + s[2] = v3_length(m[2]); + + m3x3f rot; + v3_divs( m[0], s[0], rot[0] ); + v3_divs( m[1], s[1], rot[1] ); + v3_divs( m[2], s[2], rot[2] ); + + m3x3_q( rot, q ); +} + +static void m4x3_expand_aabb_point( m4x3f m, boxf box, v3f point ){ + v3f v; + m4x3_mulv( m, point, v ); + + v3_minv( box[0], v, box[0] ); + v3_maxv( box[1], v, box[1] ); +} + +static void m4x3_expand_aabb_aabb( m4x3f m, boxf boxa, boxf boxb ){ + v3f a; v3f b; + v3_copy( boxb[0], a ); + v3_copy( boxb[1], b ); + m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], a[1], a[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], b[1], a[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], b[1], a[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], a[1], a[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], a[1], b[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], b[1], b[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], b[1], b[2] } ); + m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], a[1], b[2] } ); +} +static inline void m4x3_lookat( m4x3f m, v3f pos, v3f target, v3f up ) +{ + v3f dir; + v3_sub( target, pos, dir ); + v3_normalize( dir ); + + v3_copy( dir, m[2] ); + + v3_cross( up, m[2], m[0] ); + v3_normalize( m[0] ); + + v3_cross( m[2], m[0], m[1] ); + v3_copy( pos, m[3] ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 4.d 4x4 matrices + * ----------------------------------------------------------------------------- + */ + +#define M4X4_IDENTITY {{1.0f, 0.0f, 0.0f, 0.0f },\ + { 0.0f, 1.0f, 0.0f, 0.0f },\ + { 0.0f, 0.0f, 1.0f, 0.0f },\ + { 0.0f, 0.0f, 0.0f, 1.0f }} +#define M4X4_ZERO {{0.0f, 0.0f, 0.0f, 0.0f },\ + { 0.0f, 0.0f, 0.0f, 0.0f },\ + { 0.0f, 0.0f, 0.0f, 0.0f },\ + { 0.0f, 0.0f, 0.0f, 0.0f }} + +static void m4x4_projection( m4x4f m, f32 angle, + f32 ratio, f32 fnear, f32 ffar ) +{ + f32 scale = tanf( angle * 0.5f * VG_PIf / 180.0f ) * fnear, + r = ratio * scale, + l = -r, + t = scale, + b = -t; + + m[0][0] = 2.0f * fnear / (r - l); + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = 2.0f * fnear / (t - b); + m[1][2] = 0.0f; + m[1][3] = 0.0f; + + m[2][0] = (r + l) / (r - l); + m[2][1] = (t + b) / (t - b); + m[2][2] = -(ffar + fnear) / (ffar - fnear); + m[2][3] = -1.0f; + + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = -2.0f * ffar * fnear / (ffar - fnear); + m[3][3] = 0.0f; +} + +static void m4x4_translate( m4x4f m, v3f v ) +{ + v4_muladds( m[3], m[0], v[0], m[3] ); + v4_muladds( m[3], m[1], v[1], m[3] ); + v4_muladds( m[3], m[2], v[2], m[3] ); +} + +static inline void m4x4_copy( m4x4f a, m4x4f b ) +{ + v4_copy( a[0], b[0] ); + v4_copy( a[1], b[1] ); + v4_copy( a[2], b[2] ); + v4_copy( a[3], b[3] ); +} + +static inline void m4x4_identity( m4x4f a ) +{ + m4x4f id = M4X4_IDENTITY; + m4x4_copy( id, a ); +} + +static inline void m4x4_zero( m4x4f a ) +{ + m4x4f zero = M4X4_ZERO; + m4x4_copy( zero, a ); +} + +static inline void m4x4_mul( m4x4f a, m4x4f b, m4x4f d ) +{ + f32 a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], a03 = a[0][3], + a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], a13 = a[1][3], + a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], a23 = a[2][3], + a30 = a[3][0], a31 = a[3][1], a32 = a[3][2], a33 = a[3][3], + + b00 = b[0][0], b01 = b[0][1], b02 = b[0][2], b03 = b[0][3], + b10 = b[1][0], b11 = b[1][1], b12 = b[1][2], b13 = b[1][3], + b20 = b[2][0], b21 = b[2][1], b22 = b[2][2], b23 = b[2][3], + b30 = b[3][0], b31 = b[3][1], b32 = b[3][2], b33 = b[3][3]; + + d[0][0] = a00*b00 + a10*b01 + a20*b02 + a30*b03; + d[0][1] = a01*b00 + a11*b01 + a21*b02 + a31*b03; + d[0][2] = a02*b00 + a12*b01 + a22*b02 + a32*b03; + d[0][3] = a03*b00 + a13*b01 + a23*b02 + a33*b03; + d[1][0] = a00*b10 + a10*b11 + a20*b12 + a30*b13; + d[1][1] = a01*b10 + a11*b11 + a21*b12 + a31*b13; + d[1][2] = a02*b10 + a12*b11 + a22*b12 + a32*b13; + d[1][3] = a03*b10 + a13*b11 + a23*b12 + a33*b13; + d[2][0] = a00*b20 + a10*b21 + a20*b22 + a30*b23; + d[2][1] = a01*b20 + a11*b21 + a21*b22 + a31*b23; + d[2][2] = a02*b20 + a12*b21 + a22*b22 + a32*b23; + d[2][3] = a03*b20 + a13*b21 + a23*b22 + a33*b23; + d[3][0] = a00*b30 + a10*b31 + a20*b32 + a30*b33; + d[3][1] = a01*b30 + a11*b31 + a21*b32 + a31*b33; + d[3][2] = a02*b30 + a12*b31 + a22*b32 + a32*b33; + d[3][3] = a03*b30 + a13*b31 + a23*b32 + a33*b33; +} + +static inline void m4x4_mulv( m4x4f m, v4f v, v4f d ) +{ + v4f res; + + res[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]*v[3]; + res[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]*v[3]; + res[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]*v[3]; + res[3] = m[0][3]*v[0] + m[1][3]*v[1] + m[2][3]*v[2] + m[3][3]*v[3]; + + v4_copy( res, d ); +} + +static inline void m4x4_inv( m4x4f a, m4x4f d ) +{ + f32 a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], a03 = a[0][3], + a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], a13 = a[1][3], + a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], a23 = a[2][3], + a30 = a[3][0], a31 = a[3][1], a32 = a[3][2], a33 = a[3][3], + det, + t[6]; + + t[0] = a22*a33 - a32*a23; + t[1] = a21*a33 - a31*a23; + t[2] = a21*a32 - a31*a22; + t[3] = a20*a33 - a30*a23; + t[4] = a20*a32 - a30*a22; + t[5] = a20*a31 - a30*a21; + + d[0][0] = a11*t[0] - a12*t[1] + a13*t[2]; + d[1][0] =-(a10*t[0] - a12*t[3] + a13*t[4]); + d[2][0] = a10*t[1] - a11*t[3] + a13*t[5]; + d[3][0] =-(a10*t[2] - a11*t[4] + a12*t[5]); + + d[0][1] =-(a01*t[0] - a02*t[1] + a03*t[2]); + d[1][1] = a00*t[0] - a02*t[3] + a03*t[4]; + d[2][1] =-(a00*t[1] - a01*t[3] + a03*t[5]); + d[3][1] = a00*t[2] - a01*t[4] + a02*t[5]; + + t[0] = a12*a33 - a32*a13; + t[1] = a11*a33 - a31*a13; + t[2] = a11*a32 - a31*a12; + t[3] = a10*a33 - a30*a13; + t[4] = a10*a32 - a30*a12; + t[5] = a10*a31 - a30*a11; + + d[0][2] = a01*t[0] - a02*t[1] + a03*t[2]; + d[1][2] =-(a00*t[0] - a02*t[3] + a03*t[4]); + d[2][2] = a00*t[1] - a01*t[3] + a03*t[5]; + d[3][2] =-(a00*t[2] - a01*t[4] + a02*t[5]); + + t[0] = a12*a23 - a22*a13; + t[1] = a11*a23 - a21*a13; + t[2] = a11*a22 - a21*a12; + t[3] = a10*a23 - a20*a13; + t[4] = a10*a22 - a20*a12; + t[5] = a10*a21 - a20*a11; + + d[0][3] =-(a01*t[0] - a02*t[1] + a03*t[2]); + d[1][3] = a00*t[0] - a02*t[3] + a03*t[4]; + d[2][3] =-(a00*t[1] - a01*t[3] + a03*t[5]); + d[3][3] = a00*t[2] - a01*t[4] + a02*t[5]; + + det = 1.0f / (a00*d[0][0] + a01*d[1][0] + a02*d[2][0] + a03*d[3][0]); + v4_muls( d[0], det, d[0] ); + v4_muls( d[1], det, d[1] ); + v4_muls( d[2], det, d[2] ); + v4_muls( d[3], det, d[3] ); +} + +/* + * http://www.terathon.com/lengyel/Lengyel-Oblique.pdf + */ +static void m4x4_clip_projection( m4x4f mat, v4f plane ){ + v4f c = + { + (vg_signf(plane[0]) + mat[2][0]) / mat[0][0], + (vg_signf(plane[1]) + mat[2][1]) / mat[1][1], + -1.0f, + (1.0f + mat[2][2]) / mat[3][2] + }; + + v4_muls( plane, 2.0f / v4_dot(plane,c), c ); + + mat[0][2] = c[0]; + mat[1][2] = c[1]; + mat[2][2] = c[2] + 1.0f; + mat[3][2] = c[3]; +} + +/* + * Undoes the above operation + */ +static void m4x4_reset_clipping( m4x4f mat, float ffar, float fnear ){ + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[2][2] = -(ffar + fnear) / (ffar - fnear); + mat[3][2] = -2.0f * ffar * fnear / (ffar - fnear); +} + +/* + * ----------------------------------------------------------------------------- + * Section 5.a Boxes + * ----------------------------------------------------------------------------- + */ + +static inline void box_addpt( boxf a, v3f pt ) +{ + v3_minv( a[0], pt, a[0] ); + v3_maxv( a[1], pt, a[1] ); +} + +static inline void box_concat( boxf a, boxf b ) +{ + v3_minv( a[0], b[0], a[0] ); + v3_maxv( a[1], b[1], a[1] ); +} + +static inline void box_copy( boxf a, boxf b ) +{ + v3_copy( a[0], b[0] ); + v3_copy( a[1], b[1] ); +} + +static inline int box_overlap( boxf a, boxf b ) +{ + return + ( a[0][0] <= b[1][0] && a[1][0] >= b[0][0] ) && + ( a[0][1] <= b[1][1] && a[1][1] >= b[0][1] ) && + ( a[0][2] <= b[1][2] && a[1][2] >= b[0][2] ) + ; +} + +static int box_within( boxf greater, boxf lesser ) +{ + v3f a, b; + v3_sub( lesser[0], greater[0], a ); + v3_sub( lesser[1], greater[1], b ); + + if( (a[0] >= 0.0f) && (a[1] >= 0.0f) && (a[2] >= 0.0f) && + (b[0] <= 0.0f) && (b[1] <= 0.0f) && (b[2] <= 0.0f) ) + { + return 1; + } + + return 0; +} + +static inline void box_init_inf( boxf box ){ + v3_fill( box[0], INFINITY ); + v3_fill( box[1], -INFINITY ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 5.b Planes + * ----------------------------------------------------------------------------- + */ + +static inline void tri_to_plane( f64 a[3], f64 b[3], + f64 c[3], f64 p[4] ) +{ + f64 edge0[3]; + f64 edge1[3]; + f64 l; + + edge0[0] = b[0] - a[0]; + edge0[1] = b[1] - a[1]; + edge0[2] = b[2] - a[2]; + + edge1[0] = c[0] - a[0]; + edge1[1] = c[1] - a[1]; + edge1[2] = c[2] - a[2]; + + p[0] = edge0[1] * edge1[2] - edge0[2] * edge1[1]; + p[1] = edge0[2] * edge1[0] - edge0[0] * edge1[2]; + p[2] = edge0[0] * edge1[1] - edge0[1] * edge1[0]; + + l = sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]); + p[3] = (p[0] * a[0] + p[1] * a[1] + p[2] * a[2]) / l; + + p[0] = p[0] / l; + p[1] = p[1] / l; + p[2] = p[2] / l; +} + +static int plane_intersect3( v4f a, v4f b, v4f c, v3f p ) +{ + f32 const epsilon = 1e-6f; + + v3f x; + v3_cross( a, b, x ); + f32 d = v3_dot( x, c ); + + if( (d < epsilon) && (d > -epsilon) ) return 0; + + v3f v0, v1, v2; + v3_cross( b, c, v0 ); + v3_cross( c, a, v1 ); + v3_cross( a, b, v2 ); + + v3_muls( v0, a[3], p ); + v3_muladds( p, v1, b[3], p ); + v3_muladds( p, v2, c[3], p ); + v3_divs( p, d, p ); + + return 1; +} + +static int plane_intersect2( v4f a, v4f b, v3f p, v3f n ) +{ + f32 const epsilon = 1e-6f; + + v4f c; + v3_cross( a, b, c ); + f32 d = v3_length2( c ); + + if( (d < epsilon) && (d > -epsilon) ) + return 0; + + v3f v0, v1, vx; + v3_cross( c, b, v0 ); + v3_cross( a, c, v1 ); + + v3_muls( v0, a[3], vx ); + v3_muladds( vx, v1, b[3], vx ); + v3_divs( vx, d, p ); + v3_copy( c, n ); + + return 1; +} + +static int plane_segment( v4f plane, v3f a, v3f b, v3f co ) +{ + f32 d0 = v3_dot( a, plane ) - plane[3], + d1 = v3_dot( b, plane ) - plane[3]; + + if( d0*d1 < 0.0f ) + { + f32 tot = 1.0f/( fabsf(d0)+fabsf(d1) ); + + v3_muls( a, fabsf(d1) * tot, co ); + v3_muladds( co, b, fabsf(d0) * tot, co ); + return 1; + } + + return 0; +} + +static inline f64 plane_polarity( f64 p[4], f64 a[3] ) +{ + return + (a[0] * p[0] + a[1] * p[1] + a[2] * p[2]) + -(p[0]*p[3] * p[0] + p[1]*p[3] * p[1] + p[2]*p[3] * p[2]) + ; +} + +static f32 ray_plane( v4f plane, v3f co, v3f dir ){ + f32 d = v3_dot( plane, dir ); + if( fabsf(d) > 1e-6f ){ + v3f v0; + v3_muls( plane, plane[3], v0 ); + v3_sub( v0, co, v0 ); + return v3_dot( v0, plane ) / d; + } + else return INFINITY; +} + +/* + * ----------------------------------------------------------------------------- + * Section 5.c Closest point functions + * ----------------------------------------------------------------------------- + */ + +/* + * These closest point tests were learned from Real-Time Collision Detection by + * Christer Ericson + */ +static f32 closest_segment_segment( v3f p1, v3f q1, v3f p2, v3f q2, + f32 *s, f32 *t, v3f c1, v3f c2) +{ + v3f d1,d2,r; + v3_sub( q1, p1, d1 ); + v3_sub( q2, p2, d2 ); + v3_sub( p1, p2, r ); + + f32 a = v3_length2( d1 ), + e = v3_length2( d2 ), + f = v3_dot( d2, r ); + + const f32 kEpsilon = 0.0001f; + + if( a <= kEpsilon && e <= kEpsilon ) + { + *s = 0.0f; + *t = 0.0f; + v3_copy( p1, c1 ); + v3_copy( p2, c2 ); + + v3f v0; + v3_sub( c1, c2, v0 ); + + return v3_length2( v0 ); + } + + if( a<= kEpsilon ) + { + *s = 0.0f; + *t = vg_clampf( f / e, 0.0f, 1.0f ); + } + else + { + f32 c = v3_dot( d1, r ); + if( e <= kEpsilon ) + { + *t = 0.0f; + *s = vg_clampf( -c / a, 0.0f, 1.0f ); + } + else + { + f32 b = v3_dot(d1,d2), + d = a*e-b*b; + + if( d != 0.0f ) + { + *s = vg_clampf((b*f - c*e)/d, 0.0f, 1.0f); + } + else + { + *s = 0.0f; + } + + *t = (b*(*s)+f) / e; + + if( *t < 0.0f ) + { + *t = 0.0f; + *s = vg_clampf( -c / a, 0.0f, 1.0f ); + } + else if( *t > 1.0f ) + { + *t = 1.0f; + *s = vg_clampf((b-c)/a,0.0f,1.0f); + } + } + } + + v3_muladds( p1, d1, *s, c1 ); + v3_muladds( p2, d2, *t, c2 ); + + v3f v0; + v3_sub( c1, c2, v0 ); + return v3_length2( v0 ); +} + +static int point_inside_aabb( boxf box, v3f point ) +{ + if((point[0]<=box[1][0]) && (point[1]<=box[1][1]) && (point[2]<=box[1][2]) && + (point[0]>=box[0][0]) && (point[1]>=box[0][1]) && (point[2]>=box[0][2]) ) + return 1; + else + return 0; +} + +static void closest_point_aabb( v3f p, boxf box, v3f dest ) +{ + v3_maxv( p, box[0], dest ); + v3_minv( dest, box[1], dest ); +} + +static void closest_point_obb( v3f p, boxf box, + m4x3f mtx, m4x3f inv_mtx, v3f dest ) +{ + v3f local; + m4x3_mulv( inv_mtx, p, local ); + closest_point_aabb( local, box, local ); + m4x3_mulv( mtx, local, dest ); +} + +static f32 closest_point_segment( v3f a, v3f b, v3f point, v3f dest ) +{ + v3f v0, v1; + v3_sub( b, a, v0 ); + v3_sub( point, a, v1 ); + + f32 t = v3_dot( v1, v0 ) / v3_length2(v0); + t = vg_clampf(t,0.0f,1.0f); + v3_muladds( a, v0, t, dest ); + return t; +} + +static void closest_on_triangle( v3f p, v3f tri[3], v3f dest ) +{ + v3f ab, ac, ap; + f32 d1, d2; + + /* Region outside A */ + v3_sub( tri[1], tri[0], ab ); + v3_sub( tri[2], tri[0], ac ); + v3_sub( p, tri[0], ap ); + + d1 = v3_dot(ab,ap); + d2 = v3_dot(ac,ap); + if( d1 <= 0.0f && d2 <= 0.0f ) + { + v3_copy( tri[0], dest ); + v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); + return; + } + + /* Region outside B */ + v3f bp; + f32 d3, d4; + + v3_sub( p, tri[1], bp ); + d3 = v3_dot( ab, bp ); + d4 = v3_dot( ac, bp ); + + if( d3 >= 0.0f && d4 <= d3 ) + { + v3_copy( tri[1], dest ); + v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); + return; + } + + /* Edge region of AB */ + f32 vc = d1*d4 - d3*d2; + if( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) + { + f32 v = d1 / (d1-d3); + v3_muladds( tri[0], ab, v, dest ); + v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); + return; + } + + /* Region outside C */ + v3f cp; + f32 d5, d6; + v3_sub( p, tri[2], cp ); + d5 = v3_dot(ab, cp); + d6 = v3_dot(ac, cp); + + if( d6 >= 0.0f && d5 <= d6 ) + { + v3_copy( tri[2], dest ); + v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); + return; + } + + /* Region of AC */ + f32 vb = d5*d2 - d1*d6; + if( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f ) + { + f32 w = d2 / (d2-d6); + v3_muladds( tri[0], ac, w, dest ); + v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); + return; + } + + /* Region of BC */ + f32 va = d3*d6 - d5*d4; + if( va <= 0.0f && (d4-d3) >= 0.0f && (d5-d6) >= 0.0f ) + { + f32 w = (d4-d3) / ((d4-d3) + (d5-d6)); + v3f bc; + v3_sub( tri[2], tri[1], bc ); + v3_muladds( tri[1], bc, w, dest ); + v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); + return; + } + + /* P inside region, Q via barycentric coordinates uvw */ + f32 d = 1.0f/(va+vb+vc), + v = vb*d, + w = vc*d; + + v3_muladds( tri[0], ab, v, dest ); + v3_muladds( dest, ac, w, dest ); +} + +enum contact_type +{ + k_contact_type_default, + k_contact_type_disabled, + k_contact_type_edge +}; + +static enum contact_type closest_on_triangle_1( v3f p, v3f tri[3], v3f dest ) +{ + v3f ab, ac, ap; + f32 d1, d2; + + /* Region outside A */ + v3_sub( tri[1], tri[0], ab ); + v3_sub( tri[2], tri[0], ac ); + v3_sub( p, tri[0], ap ); + + d1 = v3_dot(ab,ap); + d2 = v3_dot(ac,ap); + if( d1 <= 0.0f && d2 <= 0.0f ) + { + v3_copy( tri[0], dest ); + return k_contact_type_default; + } + + /* Region outside B */ + v3f bp; + f32 d3, d4; + + v3_sub( p, tri[1], bp ); + d3 = v3_dot( ab, bp ); + d4 = v3_dot( ac, bp ); + + if( d3 >= 0.0f && d4 <= d3 ) + { + v3_copy( tri[1], dest ); + return k_contact_type_edge; + } + + /* Edge region of AB */ + f32 vc = d1*d4 - d3*d2; + if( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) + { + f32 v = d1 / (d1-d3); + v3_muladds( tri[0], ab, v, dest ); + return k_contact_type_edge; + } + + /* Region outside C */ + v3f cp; + f32 d5, d6; + v3_sub( p, tri[2], cp ); + d5 = v3_dot(ab, cp); + d6 = v3_dot(ac, cp); + + if( d6 >= 0.0f && d5 <= d6 ) + { + v3_copy( tri[2], dest ); + return k_contact_type_edge; + } + + /* Region of AC */ + f32 vb = d5*d2 - d1*d6; + if( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f ) + { + f32 w = d2 / (d2-d6); + v3_muladds( tri[0], ac, w, dest ); + return k_contact_type_edge; + } + + /* Region of BC */ + f32 va = d3*d6 - d5*d4; + if( va <= 0.0f && (d4-d3) >= 0.0f && (d5-d6) >= 0.0f ) + { + f32 w = (d4-d3) / ((d4-d3) + (d5-d6)); + v3f bc; + v3_sub( tri[2], tri[1], bc ); + v3_muladds( tri[1], bc, w, dest ); + return k_contact_type_edge; + } + + /* P inside region, Q via barycentric coordinates uvw */ + f32 d = 1.0f/(va+vb+vc), + v = vb*d, + w = vc*d; + + v3_muladds( tri[0], ab, v, dest ); + v3_muladds( dest, ac, w, dest ); + + return k_contact_type_default; +} + +static void closest_point_elipse( v2f p, v2f e, v2f o ) +{ + v2f pabs, ei, e2, ve, t; + + v2_abs( p, pabs ); + v2_div( (v2f){ 1.0f, 1.0f }, e, ei ); + v2_mul( e, e, e2 ); + v2_mul( ei, (v2f){ e2[0]-e2[1], e2[1]-e2[0] }, ve ); + + v2_fill( t, 0.70710678118654752f ); + + for( int i=0; i<3; i++ ){ + v2f v, u, ud, w; + + v2_mul( ve, t, v ); /* ve*t*t*t */ + v2_mul( v, t, v ); + v2_mul( v, t, v ); + + v2_sub( pabs, v, u ); + v2_normalize( u ); + + v2_mul( t, e, ud ); + v2_sub( ud, v, ud ); + + v2_muls( u, v2_length( ud ), u ); + + v2_add( v, u, w ); + v2_mul( w, ei, w ); + + v2_maxv( (v2f){0.0f,0.0f}, w, t ); + v2_normalize( t ); + } + + v2_mul( t, e, o ); + v2_copysign( o, p ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 5.d Raycasts & Spherecasts + * ----------------------------------------------------------------------------- + */ + +static int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist ) +{ + v3f v0, v1; + f32 tmin, tmax; + + v3_sub( box[0], co, v0 ); + v3_sub( box[1], co, v1 ); + + v3_mul( v0, dir_inv, v0 ); + v3_mul( v1, dir_inv, v1 ); + + tmin = vg_minf( v0[0], v1[0] ); + tmax = vg_maxf( v0[0], v1[0] ); + tmin = vg_maxf( tmin, vg_minf( v0[1], v1[1] )); + tmax = vg_minf( tmax, vg_maxf( v0[1], v1[1] )); + tmin = vg_maxf( tmin, vg_minf( v0[2], v1[2] )); + tmax = vg_minf( tmax, vg_maxf( v0[2], v1[2] )); + + return (tmax >= tmin) && (tmin <= dist) && (tmax >= 0.0f); +} + +/* Time of intersection with ray vs triangle */ +static int ray_tri( v3f tri[3], v3f co, + v3f dir, f32 *dist, int backfaces ) +{ + f32 const kEpsilon = 0.00001f; + + v3f v0, v1, h, s, q, n; + f32 a,f,u,v,t; + + f32 *pa = tri[0], + *pb = tri[1], + *pc = tri[2]; + + v3_sub( pb, pa, v0 ); + v3_sub( pc, pa, v1 ); + v3_cross( dir, v1, h ); + v3_cross( v0, v1, n ); + + if( (v3_dot( n, dir ) > 0.0f) && !backfaces ) /* Backface culling */ + return 0; + + /* Parralel */ + a = v3_dot( v0, h ); + + if( a > -kEpsilon && a < kEpsilon ) + return 0; + + f = 1.0f/a; + v3_sub( co, pa, s ); + + u = f * v3_dot(s, h); + if( u < 0.0f || u > 1.0f ) + return 0; + + v3_cross( s, v0, q ); + v = f * v3_dot( dir, q ); + if( v < 0.0f || u+v > 1.0f ) + return 0; + + t = f * v3_dot(v1, q); + if( t > kEpsilon ) + { + *dist = t; + return 1; + } + else return 0; +} + +/* time of intersection with ray vs sphere */ +static int ray_sphere( v3f c, f32 r, + v3f co, v3f dir, f32 *t ) +{ + v3f m; + v3_sub( co, c, m ); + + f32 b = v3_dot( m, dir ), + c1 = v3_dot( m, m ) - r*r; + + /* Exit if r’s origin outside s (c > 0) and r pointing away from s (b > 0) */ + if( c1 > 0.0f && b > 0.0f ) + return 0; + + f32 discr = b*b - c1; + + /* A negative discriminant corresponds to ray missing sphere */ + if( discr < 0.0f ) + return 0; + + /* + * Ray now found to intersect sphere, compute smallest t value of + * intersection + */ + *t = -b - sqrtf( discr ); + + /* If t is negative, ray started inside sphere so clamp t to zero */ + if( *t < 0.0f ) + *t = 0.0f; + + return 1; +} + +/* + * time of intersection of ray vs cylinder + * The cylinder does not have caps but is finite + * + * Heavily adapted from regular segment vs cylinder from: + * Real-Time Collision Detection + */ +static int ray_uncapped_finite_cylinder( v3f q, v3f p, f32 r, + v3f co, v3f dir, f32 *t ) +{ + v3f d, m, n, sb; + v3_muladds( co, dir, 1.0f, sb ); + + v3_sub( q, p, d ); + v3_sub( co, p, m ); + v3_sub( sb, co, n ); + + f32 md = v3_dot( m, d ), + nd = v3_dot( n, d ), + dd = v3_dot( d, d ), + nn = v3_dot( n, n ), + mn = v3_dot( m, n ), + a = dd*nn - nd*nd, + k = v3_dot( m, m ) - r*r, + c = dd*k - md*md; + + if( fabsf(a) < 0.00001f ) + { + /* Segment runs parallel to cylinder axis */ + return 0; + } + + f32 b = dd*mn - nd*md, + discr = b*b - a*c; + + if( discr < 0.0f ) + return 0; /* No real roots; no intersection */ + + *t = (-b - sqrtf(discr)) / a; + if( *t < 0.0f ) + return 0; /* Intersection behind ray */ + + /* Check within cylinder segment */ + if( md + (*t)*nd < 0.0f ) + return 0; + + if( md + (*t)*nd > dd ) + return 0; + + /* Segment intersects cylinder between the endcaps; t is correct */ + return 1; +} + +/* + * Time of intersection of sphere and triangle. Origin must be outside the + * colliding area. This is a fairly long procedure. + */ +static int spherecast_triangle( v3f tri[3], + v3f co, v3f dir, f32 r, f32 *t, v3f n ) +{ + v3f sum[3]; + v3f v0, v1; + + v3_sub( tri[1], tri[0], v0 ); + v3_sub( tri[2], tri[0], v1 ); + v3_cross( v0, v1, n ); + v3_normalize( n ); + v3_muladds( tri[0], n, r, sum[0] ); + v3_muladds( tri[1], n, r, sum[1] ); + v3_muladds( tri[2], n, r, sum[2] ); + + int hit = 0; + f32 t_min = INFINITY, + t1; + + if( ray_tri( sum, co, dir, &t1, 0 ) ){ + t_min = vg_minf( t_min, t1 ); + hit = 1; + } + + /* + * Currently disabled; ray_sphere requires |d| = 1. it is not very important. + */ +#if 0 + for( int i=0; i<3; i++ ){ + if( ray_sphere( tri[i], r, co, dir, &t1 ) ){ + t_min = vg_minf( t_min, t1 ); + hit = 1; + } + } +#endif + + for( int i=0; i<3; i++ ){ + int i0 = i, + i1 = (i+1)%3; + + if( ray_uncapped_finite_cylinder( tri[i0], tri[i1], r, co, dir, &t1 ) ){ + if( t1 < t_min ){ + t_min = t1; + + v3f co1, ct, cx; + v3_add( dir, co, co1 ); + v3_lerp( co, co1, t_min, ct ); + + closest_point_segment( tri[i0], tri[i1], ct, cx ); + v3_sub( ct, cx, n ); + v3_normalize( n ); + } + + hit = 1; + } + } + + *t = t_min; + return hit; +} + +/* + * ----------------------------------------------------------------------------- + * Section 5.e Curves + * ----------------------------------------------------------------------------- + */ + +static void eval_bezier_time( v3f p0, v3f p1, v3f h0, v3f h1, f32 t, v3f p ) +{ + f32 tt = t*t, + ttt = tt*t; + + v3_muls( p1, ttt, p ); + v3_muladds( p, h1, 3.0f*tt -3.0f*ttt, p ); + v3_muladds( p, h0, 3.0f*ttt -6.0f*tt +3.0f*t, p ); + v3_muladds( p, p0, 3.0f*tt -ttt -3.0f*t +1.0f, p ); +} + +static void eval_bezier3( v3f p0, v3f p1, v3f p2, f32 t, v3f p ) +{ + f32 u = 1.0f-t; + + v3_muls( p0, u*u, p ); + v3_muladds( p, p1, 2.0f*u*t, p ); + v3_muladds( p, p2, t*t, p ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 5.f Volumes + * ----------------------------------------------------------------------------- + */ + +static f32 vg_sphere_volume( f32 r ){ + return (4.0f/3.0f) * VG_PIf * r*r*r; +} + +static f32 vg_box_volume( boxf box ){ + v3f e; + v3_sub( box[1], box[0], e ); + return e[0]*e[1]*e[2]; +} + +static f32 vg_cylinder_volume( f32 r, f32 h ){ + return VG_PIf * r*r * h; +} + +static f32 vg_capsule_volume( f32 r, f32 h ){ + return vg_sphere_volume( r ) + vg_cylinder_volume( r, h-r*2.0f ); +} + +static void vg_sphere_bound( f32 r, boxf out_box ){ + v3_fill( out_box[0], -r ); + v3_fill( out_box[1], r ); +} + +static void vg_capsule_bound( f32 r, f32 h, boxf out_box ){ + v3_copy( (v3f){-r,-h*0.5f,r}, out_box[0] ); + v3_copy( (v3f){-r, h*0.5f,r}, out_box[1] ); +} + + +/* + * ----------------------------------------------------------------------------- + * Section 5.g Inertia Tensors + * ----------------------------------------------------------------------------- + */ + +/* + * Translate existing inertia tensor + */ +static void vg_translate_inertia( m3x3f inout_inertia, f32 mass, v3f d ){ + /* + * I = I_0 + m*[(d.d)E_3 - d(X)d] + * + * I: updated tensor + * I_0: original tensor + * m: scalar mass + * d: translation vector + * (X): outer product + * E_3: identity matrix + */ + m3x3f t, outer, scale; + m3x3_diagonal( t, v3_dot(d,d) ); + m3x3_outer_product( outer, d, d ); + m3x3_sub( t, outer, t ); + m3x3_diagonal( scale, mass ); + m3x3_mul( scale, t, t ); + m3x3_add( inout_inertia, t, inout_inertia ); +} + +/* + * Rotate existing inertia tensor + */ +static void vg_rotate_inertia( m3x3f inout_inertia, m3x3f rotation ){ + /* + * I = R I_0 R^T + * + * I: updated tensor + * I_0: original tensor + * R: rotation matrix + * R^T: tranposed rotation matrix + */ + + m3x3f Rt; + m3x3_transpose( rotation, Rt ); + m3x3_mul( rotation, inout_inertia, inout_inertia ); + m3x3_mul( inout_inertia, Rt, inout_inertia ); +} +/* + * Create inertia tensor for box + */ +static void vg_box_inertia( boxf box, f32 mass, m3x3f out_inertia ){ + v3f e, com; + v3_sub( box[1], box[0], e ); + v3_muladds( box[0], e, 0.5f, com ); + + f32 ex2 = e[0]*e[0], + ey2 = e[1]*e[1], + ez2 = e[2]*e[2], + ix = (ey2+ez2) * mass * (1.0f/12.0f), + iy = (ex2+ez2) * mass * (1.0f/12.0f), + iz = (ex2+ey2) * mass * (1.0f/12.0f); + + m3x3_identity( out_inertia ); + m3x3_setdiagonalv3( out_inertia, (v3f){ ix, iy, iz } ); + vg_translate_inertia( out_inertia, mass, com ); +} + +/* + * Create inertia tensor for sphere + */ +static void vg_sphere_inertia( f32 r, f32 mass, m3x3f out_inertia ){ + f32 ixyz = r*r * mass * (2.0f/5.0f); + + m3x3_identity( out_inertia ); + m3x3_setdiagonalv3( out_inertia, (v3f){ ixyz, ixyz, ixyz } ); +} + +/* + * Create inertia tensor for capsule + */ +static void vg_capsule_inertia( f32 r, f32 h, f32 mass, m3x3f out_inertia ){ + f32 density = mass / vg_capsule_volume( r, h ), + ch = h-r*2.0f, /* cylinder height */ + cm = VG_PIf * ch*r*r * density, /* cylinder mass */ + hm = VG_TAUf * (1.0f/3.0f) * r*r*r * density, /* hemisphere mass */ + + iy = r*r*cm * 0.5f, + ixz = iy * 0.5f + cm*ch*ch*(1.0f/12.0f), + + aux0= (hm*2.0f*r*r)/5.0f; + + iy += aux0 * 2.0f; + + f32 aux1= ch*0.5f, + aux2= aux0 + hm*(aux1*aux1 + 3.0f*(1.0f/8.0f)*ch*r); + + ixz += aux2*2.0f; + + m3x3_identity( out_inertia ); + m3x3_setdiagonalv3( out_inertia, (v3f){ ixz, iy, ixz } ); +} + +/* + * ----------------------------------------------------------------------------- + * Section 6.a PSRNG and some distributions + * ----------------------------------------------------------------------------- + */ + +/* An implementation of the MT19937 Algorithm for the Mersenne Twister + * by Evan Sultanik. Based upon the pseudocode in: M. Matsumoto and + * T. Nishimura, "Mersenne Twister: A 623-dimensionally + * equidistributed uniform pseudorandom number generator," ACM + * Transactions on Modeling and Computer Simulation Vol. 8, No. 1, + * January pp.3-30 1998. + * + * http://www.sultanik.com/Mersenne_twister + * https://github.com/ESultanik/mtwister/blob/master/mtwister.c + */ + +#define MT_UPPER_MASK 0x80000000 +#define MT_LOWER_MASK 0x7fffffff +#define MT_TEMPERING_MASK_B 0x9d2c5680 +#define MT_TEMPERING_MASK_C 0xefc60000 + +#define MT_STATE_VECTOR_LENGTH 624 + +/* changes to STATE_VECTOR_LENGTH also require changes to this */ +#define MT_STATE_VECTOR_M 397 + +typedef struct vg_rand vg_rand; +struct vg_rand { + u32 mt[MT_STATE_VECTOR_LENGTH]; + i32 index; +}; + +static void vg_rand_seed( vg_rand *rand, unsigned long seed ) { + /* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator + * from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer + * Programming," Vol. 2 (2nd Ed.) pp.102. + */ + rand->mt[0] = seed & 0xffffffff; + for( rand->index=1; rand->indexindex++){ + rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff; + } +} + +/* + * Generates a pseudo-randomly generated long. + */ +static u32 vg_randu32( vg_rand *rand ) { + u32 y; + /* mag[x] = x * 0x9908b0df for x = 0,1 */ + static u32 mag[2] = {0x0, 0x9908b0df}; + if( rand->index >= MT_STATE_VECTOR_LENGTH || rand->index < 0 ){ + /* generate STATE_VECTOR_LENGTH words at a time */ + int kk; + if( rand->index >= MT_STATE_VECTOR_LENGTH+1 || rand->index < 0 ){ + vg_rand_seed( rand, 4357 ); + } + for( kk=0; kkmt[kk] & MT_UPPER_MASK) | + (rand->mt[kk+1] & MT_LOWER_MASK); + rand->mt[kk] = rand->mt[kk+MT_STATE_VECTOR_M] ^ (y>>1) ^ mag[y & 0x1]; + } + for( ; kkmt[kk] & MT_UPPER_MASK) | + (rand->mt[kk+1] & MT_LOWER_MASK); + rand->mt[kk] = + rand->mt[ kk+(MT_STATE_VECTOR_M-MT_STATE_VECTOR_LENGTH)] ^ + (y >> 1) ^ mag[y & 0x1]; + } + y = (rand->mt[MT_STATE_VECTOR_LENGTH-1] & MT_UPPER_MASK) | + (rand->mt[0] & MT_LOWER_MASK); + rand->mt[MT_STATE_VECTOR_LENGTH-1] = + rand->mt[MT_STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1]; + rand->index = 0; + } + y = rand->mt[rand->index++]; + y ^= (y >> 11); + y ^= (y << 7) & MT_TEMPERING_MASK_B; + y ^= (y << 15) & MT_TEMPERING_MASK_C; + y ^= (y >> 18); + return y; +} + +/* + * Generates a pseudo-randomly generated f64 in the range [0..1]. + */ +static inline f64 vg_randf64( vg_rand *rand ){ + return (f64)vg_randu32(rand)/(f64)0xffffffff; +} + +static inline f64 vg_randf64_range( vg_rand *rand, f64 min, f64 max ){ + return vg_lerp( min, max, (f64)vg_randf64(rand) ); +} + +static inline void vg_rand_dir( vg_rand *rand, v3f dir ){ + dir[0] = vg_randf64(rand); + dir[1] = vg_randf64(rand); + dir[2] = vg_randf64(rand); + + /* warning: *could* be 0 length. + * very unlikely.. 1 in (2^32)^3. but its mathematically wrong. */ + + v3_muls( dir, 2.0f, dir ); + v3_sub( dir, (v3f){1.0f,1.0f,1.0f}, dir ); + + v3_normalize( dir ); +} + +static inline void vg_rand_sphere( vg_rand *rand, v3f co ){ + vg_rand_dir(rand,co); + v3_muls( co, cbrtf( vg_randf64(rand) ), co ); +} + +static void vg_rand_disc( vg_rand *rand, v2f co ){ + f32 a = vg_randf64(rand) * VG_TAUf; + co[0] = sinf(a); + co[1] = cosf(a); + v2_muls( co, sqrtf( vg_randf64(rand) ), co ); +} + +static void vg_rand_cone( vg_rand *rand, v3f out_dir, f32 angle ){ + f32 r = sqrtf(vg_randf64(rand)) * angle * 0.5f, + a = vg_randf64(rand) * VG_TAUf; + + out_dir[0] = sinf(a) * sinf(r); + out_dir[1] = cosf(a) * sinf(r); + out_dir[2] = cosf(r); +} + +static void vg_hsv_rgb( v3f hsv, v3f rgb ){ + i32 i = floorf( hsv[0]*6.0f ); + f32 v = hsv[2], + f = hsv[0] * 6.0f - (f32)i, + p = v * (1.0f-hsv[1]), + q = v * (1.0f-f*hsv[1]), + t = v * (1.0f-(1.0f-f)*hsv[1]); + + switch( i % 6 ){ + case 0: rgb[0] = v; rgb[1] = t; rgb[2] = p; break; + case 1: rgb[0] = q; rgb[1] = v; rgb[2] = p; break; + case 2: rgb[0] = p; rgb[1] = v; rgb[2] = t; break; + case 3: rgb[0] = p; rgb[1] = q; rgb[2] = v; break; + case 4: rgb[0] = t; rgb[1] = p; rgb[2] = v; break; + case 5: rgb[0] = v; rgb[1] = p; rgb[2] = q; break; + } +} + +static void vg_rgb_hsv( v3f rgb, v3f hsv ){ + f32 min = v3_minf( rgb ), + max = v3_maxf( rgb ), + range = max-min, + k_epsilon = 0.00001f; + + hsv[2] = max; + if( range < k_epsilon ){ + hsv[0] = 0.0f; + hsv[1] = 0.0f; + return; + } + + if( max > k_epsilon ){ + hsv[1] = range/max; + } + else { + hsv[0] = 0.0f; + hsv[1] = 0.0f; + return; + } + + if( rgb[0] >= max ) + hsv[0] = (rgb[1]-rgb[2])/range; + else if( max == rgb[1] ) + hsv[0] = 2.0f+(rgb[2]-rgb[0])/range; + else + hsv[0] = 4.0f+(rgb[0]-rgb[1])/range; + + hsv[0] = vg_fractf( hsv[0] * (60.0f/360.0f) ); +} diff --git a/vg_mem.c b/vg_mem.c new file mode 100644 index 0000000..7974079 --- /dev/null +++ b/vg_mem.c @@ -0,0 +1,348 @@ +#pragma once + +#include "vg_platform.h" +#include "vg_log.h" +#include "vg_mem.h" + +#include +#include + +struct vg_global_mem vg_mem; + +u32 vg_align8( u32 s ) +{ + u32 m = (s + 7) >> 3; + return m << 3; +} + +u32 vg_align4( u32 s ) +{ + u32 m = (s + 3) >> 2; + return m << 2; +} + +/* Returns allocator structure from data pointer */ +vg_linear_allocator *vg_linear_header( void *data ) +{ + vg_linear_allocator *ptr = data; + ptr --; + + return ptr; +} + +/* allocate something from a linear allocator */ +__attribute__((warn_unused_result)) +void *_vg_linear_alloc( void *buffer, u32 size, const char *constr_name ) +{ + if( size % 8 ){ + vg_error( "alloc(%u) is not 8 byte aligned\n", size ); + vg_print_backtrace(); + size = vg_align8( size ); + } + if( ((u64)buffer) % 8 ){ + vg_fatal_error( "unaligned buffer (%p)", buffer ); + } + + vg_linear_allocator *alloc = vg_linear_header( buffer ); + + if( (alloc->cur + size) > alloc->size ){ + vg_fatal_error( "linear allocator overflow (%u + %u > %u)\n", + alloc->cur, size, alloc->size ); + } + + if( alloc->flags & VG_MEMORY_SYSTEM ) + if( (alloc->allocation_count + 1) > VG_MAX_ALLOCATIONS ) + vg_fatal_error( "Max linear allocations reached" ); + + void *data; + + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + data = malloc( size ); + + vg_allocation_meta *meta = &alloc->alloc_table[ alloc->allocation_count ]; + meta->type = k_allocation_type_block; + meta->data = data; + meta->size = size; + meta->name = constr_name; + } + else{ + data = buffer + alloc->cur; + } + + u8 *bytes = data; + for( u32 i=0; iallocation_count ++; + alloc->last_alloc = data; + alloc->last_alloc_size = size; + alloc->cur += size; + + if( ((u64)data) % 8 ){ + vg_fatal_error( "unaligned" ); + } + + return data; +} + +/* resize latest block of memory from linear */ +__attribute__((warn_unused_result)) +void *vg_linear_resize( void *buffer, void *data, u32 newsize ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + + if( newsize % 8 ){ + vg_error( "alloc(%u) is not 8 byte aligned\n", newsize ); + vg_print_backtrace(); + newsize = vg_align8( newsize ); + } + + if( alloc->last_alloc != data ) + vg_fatal_error( "This block has been fixed!" ); + + if( (alloc->cur - alloc->last_alloc_size + newsize) > alloc->size ) + vg_fatal_error( "Cannot resize, overflow" ); + + alloc->cur -= alloc->last_alloc_size; + alloc->cur += newsize; + alloc->last_alloc_size = newsize; + + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + data = realloc( data, newsize ); + if( !data ) + vg_fatal_error( "realloc failed" ); + + alloc->alloc_table[ alloc->allocation_count-1 ].data = data; + alloc->last_alloc = data; + return data; + } + else{ + return data; + } +} + +/* its possible to delete just the last item */ +void vg_linear_del( void *buffer, void *data ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + + if( alloc->last_alloc != data ){ + vg_fatal_error( "This block has been fixed! Last alloc: %p, this: %p\n", + alloc->last_alloc, data ); + } + + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + vg_allocation_meta *meta = &alloc->alloc_table[alloc->allocation_count-1]; + if( meta->type == k_allocation_type_linear ) + vg_fatal_error( "Cannot free a linear allocator in this conext" ); + + free( data ); + } + + alloc->allocation_count --; + alloc->cur -= alloc->last_alloc_size; + alloc->last_alloc = NULL; + alloc->last_alloc_size = 0; +} + +/* extend latest block of memory from linear */ +__attribute__((warn_unused_result)) +void *_vg_linear_extend( void *buffer, void *data, u32 extra, + const char *constr_name ) +{ + if( !data ) + return _vg_linear_alloc( buffer, vg_align8(extra), constr_name ); + + vg_linear_allocator *alloc = vg_linear_header( buffer ); + + if( alloc->last_alloc != data ) + vg_fatal_error( "This block has been fixed!" ); + + u32 new_size = alloc->last_alloc_size + extra; + return vg_linear_resize( buffer, data, vg_align8(new_size) ); +} + +/* get the current usage of allocator */ +u32 vg_linear_get_cur( void *buffer ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + return alloc->cur; +} + +/* get the capacity of allocator. */ +u32 vg_linear_get_capacity( void *buffer ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + return alloc->size; +} + +/* get the remaining size of the allocator */ +u32 vg_linear_remaining( void *buffer ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + return alloc->size - alloc->cur; +} + +/* yeet all memory from linear allocator */ +void vg_linear_clear( void *buffer ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + + /* libc mode we recursively free any allocations made */ + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + for( u32 i=0; iallocation_count; i++ ){ + vg_allocation_meta *meta = &alloc->alloc_table[i]; + + if( meta->type == k_allocation_type_block ){ + free( meta->data ); + } + else{ + vg_linear_clear( meta->data ); + vg_linear_allocator *sub = vg_linear_header( meta->data ); + + free( sub->alloc_table ); + free( sub ); + } + } + } + + alloc->last_alloc = NULL; + alloc->last_alloc_size = 0; + alloc->allocation_count = 0; + alloc->cur = 0; +} + +/* allocate a FIXED SIZE linear allocator + * + * FIXME: there was a bug in vg's code that caused a race condition between + * two system allocations. make this IMPOSSIBLE by requiring a lock + * on the allocater to be passed between threads. + * + * luckily that bug only exists when using development tools, but still! + * + * this should then only be checked and turned on in debugging. + * + */ +void *_vg_create_linear_allocator( void *lin_alloc, u32 size, + u16 flags, const char *constr_name) +{ + if( sizeof( vg_linear_allocator ) != 32 ) + vg_fatal_error( "Programming error" ); + + vg_linear_allocator *header; + u32 block_size = size + sizeof(vg_linear_allocator); + + /* Creating it inside an existing one */ + if( lin_alloc ){ + vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); + + if( alloc->cur + block_size > alloc->size ) + vg_fatal_error( "Out of memory" ); + + if( alloc->allocation_count + 1 > VG_MAX_ALLOCATIONS ) + vg_fatal_error( "Max allocations in linear allocator" ); + + if( (flags && VG_MEMORY_SYSTEM) && (alloc->flags & VG_MEMORY_REALTIME) ) + vg_fatal_error( "Cannot declare realtime allocator inside systems" + " allocator" ); + + if( vg_mem.use_libc_malloc ){ + vg_allocation_meta *meta = + &alloc->alloc_table[ alloc->allocation_count ]; + + if( flags & VG_MEMORY_REALTIME ) + header = malloc( block_size ); + else + header = malloc( sizeof(vg_linear_allocator) ); + + meta->data = header+1; + meta->type = k_allocation_type_linear; + meta->size = size; + meta->name = constr_name; + } + else{ + header = lin_alloc + alloc->cur; + } + + alloc->cur += block_size; + alloc->last_alloc = header; + alloc->last_alloc_size = block_size; + alloc->allocation_count ++; + } + else{ + if( vg_mem.use_libc_malloc && (flags & VG_MEMORY_SYSTEM) ) + header = malloc( sizeof(vg_linear_allocator) ); + else + header = malloc( block_size ); + } + + header->allocation_count = 0; + header->cur = 0; + header->last_alloc = NULL; + header->last_alloc_size = 0; + header->size = size; + header->flags = flags; + + if( vg_mem.use_libc_malloc && (flags & VG_MEMORY_SYSTEM) ){ + u32 table_size = sizeof(vg_allocation_meta)*VG_MAX_ALLOCATIONS; + header->alloc_table = malloc( table_size ); + } + else + header->alloc_table = NULL; + + return header+1; +} + +/* request all the memory we need in advance */ +void vg_set_mem_quota( u32 size ) +{ + vg_mem.quota = size; +} + +void vg_alloc_quota(void) +{ + u32 size_scratch = 10*1024*1024; + u32 size = VG_MAX( vg_mem.quota, size_scratch ); + + vg_mem.rtmemory = _vg_create_linear_allocator( NULL, size, VG_MEMORY_SYSTEM, + "VG Root" ); + vg_mem.scratch = _vg_create_linear_allocator( vg_mem.rtmemory, + size_scratch, + VG_MEMORY_SYSTEM, + "Scratch buffer" ); +} + +void vg_mem_log( void *lin_alloc, int depth, const char *name ) +{ + if( vg_mem.use_libc_malloc ){ + vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); + + u32 s = alloc->size; + f32 p = ((float)alloc->cur / (float)alloc->size) * 100.0f; + + for( int i=0; iflags & VG_MEMORY_SYSTEM ){ + for( u32 i=0; iallocation_count; i++ ){ + vg_allocation_meta *meta = &alloc->alloc_table[i]; + + if( meta->type == k_allocation_type_block ){ + for( int i=0; iname, meta->size ); + } + else{ + vg_mem_log( meta->data, depth +1, meta->name ); + } + } + } + else{ + for( int i=0; i (UNTRACKED)\n" ); + } + } + else{ + vg_error( "allocations are not tracked (turn on libc mode)\n" ); + } +} diff --git a/vg_mem.h b/vg_mem.h index 6aa22f2..6fde883 100644 --- a/vg_mem.h +++ b/vg_mem.h @@ -1,30 +1,19 @@ -#ifndef VG_MEM_H -#define VG_MEM_H - -#include "vg.h" -#include "vg_stdint.h" -#include "vg_platform.h" -#include "vg_log.h" - -#include -#include - -static void vg_print_backtrace(void); +#pragma once #define VG_MAX_ALLOCATIONS 128 -#define VG_FUZZ_ALLOCATIONS typedef struct vg_linear_allocator vg_linear_allocator; typedef struct vg_allocation_meta vg_allocation_meta; -struct{ +struct vg_global_mem +{ void *rtmemory, *scratch; int use_libc_malloc; u32 quota; } -static vg_mem; +extern vg_mem; struct vg_allocation_meta { @@ -41,8 +30,6 @@ struct vg_allocation_meta #define VG_MEMORY_SYSTEM 0x1 /* systems memory, slow and low counts */ #define VG_MEMORY_REALTIME 0x2 /* per-frame. no max allocs, only size. fast */ -/* systems memory cannot be declared inside realtime memory regions */ - /* * Stored just behind the array. 32 bytes. */ @@ -56,54 +43,48 @@ struct vg_linear_allocator u32 last_alloc_size; void *last_alloc; vg_allocation_meta *alloc_table; - -#ifdef _WIN32 - /* 32 bit pointers! */ - u8 padding[ 8 ]; -#endif }; #pragma pack(pop) -static u32 vg_align8( u32 s ); -static u32 vg_align4( u32 s ); +u32 vg_align8( u32 s ); +u32 vg_align4( u32 s ); /* allocate something from a linear allocator */ __attribute__((warn_unused_result)) -static void *_vg_linear_alloc( void *buffer, u32 size, - const char *constr_name ); +void *_vg_linear_alloc( void *buffer, u32 size, const char *constr_name ); /* resize latest block of memory from linear */ __attribute__((warn_unused_result)) -static void *vg_linear_resize( void *buffer, void *data, u32 newsize ); +void *vg_linear_resize( void *buffer, void *data, u32 newsize ); /* its possible to delete just the last item */ -static void vg_linear_del( void *buffer, void *data ); +void vg_linear_del( void *buffer, void *data ); /* extend latest block of memory from linear */ __attribute__((warn_unused_result)) -static void *_vg_linear_extend( void *buffer, void *data, u32 extra, - const char *constr_name ); +void *_vg_linear_extend( void *buffer, void *data, u32 extra, + const char *constr_name ); /* get the current usage of allocator */ -static u32 vg_linear_get_cur( void *buffer ); +u32 vg_linear_get_cur( void *buffer ); /* get the capacity of allocator. */ -static u32 vg_linear_get_capacity( void *buffer ); +u32 vg_linear_get_capacity( void *buffer ); /* get the remaining size of the allocator */ -static u32 vg_linear_remaining( void *buffer ); +u32 vg_linear_remaining( void *buffer ); /* yeet all memory from linear allocator */ -static void vg_linear_clear( void *buffer ); +void vg_linear_clear( void *buffer ); /* request all the memory we need in advance */ -static void vg_set_mem_quota( u32 size ); +void vg_set_mem_quota( u32 size ); /* essentially init() */ -static void vg_alloc_quota(void); +void vg_alloc_quota(void); /* print out tree of current memory used. only works with libc mode */ -static void vg_mem_log( void *lin_alloc, int depth, const char *name ); +void vg_mem_log( void *lin_alloc, int depth, const char *name ); #define VG_MEM_MCSTR(S) VG_MEM_MCSTR2(S) #define VG_MEM_MCSTR2(S) #S @@ -115,353 +96,6 @@ static void vg_mem_log( void *lin_alloc, int depth, const char *name ); #define vg_create_linear_allocator(...) \ _vg_create_linear_allocator( __VA_ARGS__, __FILE__":"VG_MEM_MCSTR(__LINE__) ) -/* implementation - * ---------------------------------------- - */ - -static void vg_fatal_error( const char *fmt, ... ); - -#if 0 -static void vg_error(const char *fmt, ...); -static void vg_info(const char *fmt, ...); -#endif - -static u32 vg_align8( u32 s ) -{ - u32 m = (s + 7) >> 3; - return m << 3; -} - -static u32 vg_align4( u32 s ) -{ - u32 m = (s + 3) >> 2; - return m << 2; -} - -/* Returns allocator structure from data pointer */ -static vg_linear_allocator *vg_linear_header( void *data ) -{ - vg_linear_allocator *ptr = data; - ptr --; - - return ptr; -} - -/* allocate something from a linear allocator */ -__attribute__((warn_unused_result)) -static void *_vg_linear_alloc( void *buffer, u32 size, - const char *constr_name ) -{ - if( size % 8 ){ - vg_error( "alloc(%u) is not 8 byte aligned\n", size ); - vg_print_backtrace(); - size = vg_align8( size ); - } - if( ((u64)buffer) % 8 ){ - vg_fatal_error( "unaligned buffer (%p)", buffer ); - } - - vg_linear_allocator *alloc = vg_linear_header( buffer ); - - if( (alloc->cur + size) > alloc->size ){ - vg_fatal_error( "linear allocator overflow (%u + %u > %u)\n", - alloc->cur, size, alloc->size ); - } - - if( alloc->flags & VG_MEMORY_SYSTEM ) - if( (alloc->allocation_count + 1) > VG_MAX_ALLOCATIONS ) - vg_fatal_error( "Max linear allocations reached" ); - - void *data; - - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ - data = malloc( size ); - - vg_allocation_meta *meta = &alloc->alloc_table[ alloc->allocation_count ]; - meta->type = k_allocation_type_block; - meta->data = data; - meta->size = size; - meta->name = constr_name; - } - else{ - data = buffer + alloc->cur; - } - - u8 *bytes = data; - for( u32 i=0; iallocation_count ++; - alloc->last_alloc = data; - alloc->last_alloc_size = size; - alloc->cur += size; - - if( ((u64)data) % 8 ){ - vg_fatal_error( "unaligned" ); - } - - return data; -} - -/* resize latest block of memory from linear */ -__attribute__((warn_unused_result)) -static void *vg_linear_resize( void *buffer, void *data, u32 newsize ) -{ - vg_linear_allocator *alloc = vg_linear_header( buffer ); - - if( newsize % 8 ){ - vg_error( "alloc(%u) is not 8 byte aligned\n", newsize ); - vg_print_backtrace(); - newsize = vg_align8( newsize ); - } - - if( alloc->last_alloc != data ) - vg_fatal_error( "This block has been fixed!" ); - - if( (alloc->cur - alloc->last_alloc_size + newsize) > alloc->size ) - vg_fatal_error( "Cannot resize, overflow" ); - - alloc->cur -= alloc->last_alloc_size; - alloc->cur += newsize; - alloc->last_alloc_size = newsize; - - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ - data = realloc( data, newsize ); - if( !data ) - vg_fatal_error( "realloc failed" ); - - alloc->alloc_table[ alloc->allocation_count-1 ].data = data; - alloc->last_alloc = data; - return data; - } - else{ - return data; - } -} - -/* its possible to delete just the last item */ -static void vg_linear_del( void *buffer, void *data ) -{ - vg_linear_allocator *alloc = vg_linear_header( buffer ); - - if( alloc->last_alloc != data ){ - vg_fatal_error( "This block has been fixed! Last alloc: %p, this: %p\n", - alloc->last_alloc, data ); - } - - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ - vg_allocation_meta *meta = &alloc->alloc_table[alloc->allocation_count-1]; - if( meta->type == k_allocation_type_linear ) - vg_fatal_error( "Cannot free a linear allocator in this conext" ); - - free( data ); - } - - alloc->allocation_count --; - alloc->cur -= alloc->last_alloc_size; - alloc->last_alloc = NULL; - alloc->last_alloc_size = 0; -} - -/* extend latest block of memory from linear */ -__attribute__((warn_unused_result)) -static void *_vg_linear_extend( void *buffer, void *data, u32 extra, - const char *constr_name ) -{ - if( !data ) - return _vg_linear_alloc( buffer, vg_align8(extra), constr_name ); - - vg_linear_allocator *alloc = vg_linear_header( buffer ); - - if( alloc->last_alloc != data ) - vg_fatal_error( "This block has been fixed!" ); - - u32 new_size = alloc->last_alloc_size + extra; - return vg_linear_resize( buffer, data, vg_align8(new_size) ); -} - -/* get the current usage of allocator */ -static u32 vg_linear_get_cur( void *buffer ) -{ - vg_linear_allocator *alloc = vg_linear_header( buffer ); - return alloc->cur; -} - -/* get the capacity of allocator. */ -static u32 vg_linear_get_capacity( void *buffer ) -{ - vg_linear_allocator *alloc = vg_linear_header( buffer ); - return alloc->size; -} - -/* get the remaining size of the allocator */ -static u32 vg_linear_remaining( void *buffer ) -{ - vg_linear_allocator *alloc = vg_linear_header( buffer ); - return alloc->size - alloc->cur; -} - -/* yeet all memory from linear allocator */ -static void vg_linear_clear( void *buffer ) -{ - vg_linear_allocator *alloc = vg_linear_header( buffer ); - - /* libc mode we recursively free any allocations made */ - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ - for( u32 i=0; iallocation_count; i++ ){ - vg_allocation_meta *meta = &alloc->alloc_table[i]; - - if( meta->type == k_allocation_type_block ){ - free( meta->data ); - } - else{ - vg_linear_clear( meta->data ); - vg_linear_allocator *sub = vg_linear_header( meta->data ); - - free( sub->alloc_table ); - free( sub ); - } - } - } - - alloc->last_alloc = NULL; - alloc->last_alloc_size = 0; - alloc->allocation_count = 0; - alloc->cur = 0; -} - -/* allocate a FIXED SIZE linear allocator - * - * FIXME: there was a bug in vg's code that caused a race condition between - * two system allocations. make this IMPOSSIBLE by requiring a lock - * on the allocater to be passed between threads. - * - * luckily that bug only exists when using development tools, but still! - * - * this should then only be checked and turned on in debugging. - * - */ -static void *_vg_create_linear_allocator( void *lin_alloc, u32 size, - u16 flags, const char *constr_name) -{ - assert( sizeof( vg_linear_allocator ) == 32 ); - - vg_linear_allocator *header; - u32 block_size = size + sizeof(vg_linear_allocator); - - /* Creating it inside an existing one */ - if( lin_alloc ){ - vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); - - if( alloc->cur + block_size > alloc->size ) - vg_fatal_error( "Out of memory" ); - - if( alloc->allocation_count + 1 > VG_MAX_ALLOCATIONS ) - vg_fatal_error( "Max allocations in linear allocator" ); - - if( (flags && VG_MEMORY_SYSTEM) && (alloc->flags & VG_MEMORY_REALTIME) ) - vg_fatal_error( "Cannot declare realtime allocator inside systems" - " allocator" ); - - if( vg_mem.use_libc_malloc ){ - vg_allocation_meta *meta = - &alloc->alloc_table[ alloc->allocation_count ]; - - if( flags & VG_MEMORY_REALTIME ) - header = malloc( block_size ); - else - header = malloc( sizeof(vg_linear_allocator) ); - - meta->data = header+1; - meta->type = k_allocation_type_linear; - meta->size = size; - meta->name = constr_name; - } - else{ - header = lin_alloc + alloc->cur; - } - - alloc->cur += block_size; - alloc->last_alloc = header; - alloc->last_alloc_size = block_size; - alloc->allocation_count ++; - } - else{ - if( vg_mem.use_libc_malloc && (flags & VG_MEMORY_SYSTEM) ) - header = malloc( sizeof(vg_linear_allocator) ); - else - header = malloc( block_size ); - } - - header->allocation_count = 0; - header->cur = 0; - header->last_alloc = NULL; - header->last_alloc_size = 0; - header->size = size; - header->flags = flags; - - if( vg_mem.use_libc_malloc && (flags & VG_MEMORY_SYSTEM) ){ - u32 table_size = sizeof(vg_allocation_meta)*VG_MAX_ALLOCATIONS; - header->alloc_table = malloc( table_size ); - } - else - header->alloc_table = NULL; - - return header+1; -} - -/* request all the memory we need in advance */ -static void vg_set_mem_quota( u32 size ) -{ - vg_mem.quota = size; -} - -static void vg_alloc_quota(void) -{ - u32 size_scratch = 10*1024*1024; - u32 size = VG_MAX( vg_mem.quota, size_scratch ); - - vg_mem.rtmemory = _vg_create_linear_allocator( NULL, size, VG_MEMORY_SYSTEM, - "VG Root" ); - vg_mem.scratch = _vg_create_linear_allocator( vg_mem.rtmemory, - size_scratch, - VG_MEMORY_SYSTEM, - "Scratch buffer" ); -} - -static void vg_mem_log( void *lin_alloc, int depth, const char *name ) -{ - if( vg_mem.use_libc_malloc ){ - vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); - - u32 s = alloc->size; - f32 p = ((float)alloc->cur / (float)alloc->size) * 100.0f; - - for( int i=0; iflags & VG_MEMORY_SYSTEM ){ - for( u32 i=0; iallocation_count; i++ ){ - vg_allocation_meta *meta = &alloc->alloc_table[i]; - - if( meta->type == k_allocation_type_block ){ - for( int i=0; iname, meta->size ); - } - else{ - vg_mem_log( meta->data, depth +1, meta->name ); - } - } - } - else{ - for( int i=0; i (UNTRACKED)\n" ); - } - } - else{ - vg_error( "allocations are not tracked (turn on libc mode)\n" ); - } -} - -#endif /* VG_MEM_H */ +void *_vg_create_linear_allocator( void *lin_alloc, u32 size, + u16 flags, const char *constr_name); +vg_linear_allocator *vg_linear_header( void *data ); diff --git a/vg_mem_pool.c b/vg_mem_pool.c new file mode 100644 index 0000000..1fe3302 --- /dev/null +++ b/vg_mem_pool.c @@ -0,0 +1,100 @@ +#include "vg_platform.h" +#include "vg_mem.h" +#include "vg_mem_pool.h" +#include + +/* implementation + * -------------------------------------------------------------------------- */ + +vg_pool_node *vg_pool_nodeptr( vg_pool *pool, u16 id ){ + if( !id ) return NULL; + else { + return pool->buffer + (pool->stride*(id-1)) + pool->offset; + } +} + +void *vg_pool_item( vg_pool *pool, u16 id ) +{ + if( (id == 0) || (id > pool->count) ) + return NULL; + + return pool->buffer + pool->stride*(size_t)(id-1); +} + +void vg_pool_init( vg_pool *pool ) +{ + pool->head = 1; + pool->tail = pool->count; + for( u16 ib=1; ib <= pool->count; ib++ ){ + vg_pool_node *nb = vg_pool_nodeptr( pool, ib ); + + u16 ia = ib-1, ic = ib+1; + nb->l = ia; + nb->r = ic<=pool->count? ic: 0; + nb->ref_count = 0; + } +} + +u16 vg_pool_id( vg_pool *pool, void *item ) +{ + return ((item - pool->buffer) / pool->stride) + 1; +} + +static void vg_pool_unlink( vg_pool *pool, u16 id ) +{ + vg_pool_node *node = vg_pool_nodeptr( pool, id ); + vg_pool_node *l = vg_pool_nodeptr( pool, node->l ), + *r = vg_pool_nodeptr( pool, node->r ); + + if( pool->head == id ) pool->head = node->r; + if( pool->tail == id ) pool->tail = node->l; + + if( l ) l->r = node->r; + if( r ) r->l = node->l; + + node->r = 0; + node->l = 0; +} + +u16 vg_pool_lru( vg_pool *pool ) +{ + u16 head = pool->head; + if( !head ) return 0; + + vg_pool_unlink( pool, head ); + return head; +} + +void vg_pool_watch( vg_pool *pool, u16 id ) +{ + vg_pool_node *node = vg_pool_nodeptr( pool, id ); + + if( !node->ref_count ){ + vg_pool_unlink( pool, id ); + } + + if( node->ref_count == 0xffff ) + vg_fatal_error( "pool watch missmatch (limit is 128)\n" ); + + node->ref_count ++; +} + +/* if after this no more watches, places back into the volatile list */ +void vg_pool_unwatch( vg_pool *pool, u16 id ) +{ + vg_pool_node *node = vg_pool_nodeptr( pool, id ); + + if( node->ref_count == 0 ) + vg_fatal_error( "pool unwatch missmatch (no watchers)\n" ); + + node->ref_count --; + if( !node->ref_count ){ + vg_pool_node *head = vg_pool_nodeptr( pool, pool->head ), + *tail = vg_pool_nodeptr( pool, pool->tail ); + + if( tail ) tail->r = id; + node->l = pool->tail; + pool->tail = id; + if( !head ) pool->head = id; + } +} diff --git a/vg_mem_pool.h b/vg_mem_pool.h index 412d2c0..3a61881 100644 --- a/vg_mem_pool.h +++ b/vg_mem_pool.h @@ -1,119 +1,26 @@ -#ifndef VG_MEM_POOL_H -#define VG_MEM_POOL_H - -#include "vg_mem.h" -#include "stddef.h" -#include "vg_stdint.h" +#pragma once +#include "vg_platform.h" typedef struct vg_pool vg_pool; typedef struct vg_pool_node vg_pool_node; /* this goes in your structures */ -struct vg_pool_node { +struct vg_pool_node +{ u16 l, r, ref_count; }; -struct vg_pool { +struct vg_pool +{ void *buffer; /* array which holds the real data */ u16 count, head, tail; size_t stride, offset; }; -static vg_pool_node *vg_pool_nodeptr ( vg_pool *pool, u16 id ); -static void *vg_pool_item ( vg_pool *pool, u16 id ); -static void vg_pool_init ( vg_pool *pool ); -static u16 vg_pool_id ( vg_pool *pool, void *item ); -static u16 vg_pool_lru ( vg_pool *pool ); -static void vg_pool_watch ( vg_pool *pool, u16 id ); -static void vg_pool_unwatch ( vg_pool *pool, u16 id ); - -/* implementation - * -------------------------------------------------------------------------- */ - -static vg_pool_node *vg_pool_nodeptr( vg_pool *pool, u16 id ){ - if( !id ) return NULL; - else { - return pool->buffer + (pool->stride*(id-1)) + pool->offset; - } -} - -static void *vg_pool_item( vg_pool *pool, u16 id ){ - if( (id == 0) || (id > pool->count) ) - return NULL; - - return pool->buffer + pool->stride*(size_t)(id-1); -} - -static void vg_pool_init( vg_pool *pool ){ - pool->head = 1; - pool->tail = pool->count; - for( u16 ib=1; ib <= pool->count; ib++ ){ - vg_pool_node *nb = vg_pool_nodeptr( pool, ib ); - - u16 ia = ib-1, ic = ib+1; - nb->l = ia; - nb->r = ic<=pool->count? ic: 0; - nb->ref_count = 0; - } -} - -static u16 vg_pool_id( vg_pool *pool, void *item ){ - return ((item - pool->buffer) / pool->stride) + 1; -} - -static void vg_pool_unlink( vg_pool *pool, u16 id ){ - vg_pool_node *node = vg_pool_nodeptr( pool, id ); - vg_pool_node *l = vg_pool_nodeptr( pool, node->l ), - *r = vg_pool_nodeptr( pool, node->r ); - - if( pool->head == id ) pool->head = node->r; - if( pool->tail == id ) pool->tail = node->l; - - if( l ) l->r = node->r; - if( r ) r->l = node->l; - - node->r = 0; - node->l = 0; -} - -static u16 vg_pool_lru( vg_pool *pool ){ - u16 head = pool->head; - if( !head ) return 0; - - vg_pool_unlink( pool, head ); - return head; -} - -static void vg_pool_watch( vg_pool *pool, u16 id ){ - vg_pool_node *node = vg_pool_nodeptr( pool, id ); - - if( !node->ref_count ){ - vg_pool_unlink( pool, id ); - } - - if( node->ref_count >= 128 ) - vg_fatal_error( "pool watch missmatch (limit is 128)\n" ); - - node->ref_count ++; -} - -/* if after this no more watches, places back into the volatile list */ -static void vg_pool_unwatch( vg_pool *pool, u16 id ){ - vg_pool_node *node = vg_pool_nodeptr( pool, id ); - - if( node->ref_count == 0 ) - vg_fatal_error( "pool unwatch missmatch (no watchers)\n" ); - - node->ref_count --; - if( !node->ref_count ){ - vg_pool_node *head = vg_pool_nodeptr( pool, pool->head ), - *tail = vg_pool_nodeptr( pool, pool->tail ); - - if( tail ) tail->r = id; - node->l = pool->tail; - pool->tail = id; - if( !head ) pool->head = id; - } -} - -#endif /* VG_MEM_POOL_H */ +vg_pool_node *vg_pool_nodeptr ( vg_pool *pool, u16 id ); +void *vg_pool_item ( vg_pool *pool, u16 id ); +void vg_pool_init ( vg_pool *pool ); +u16 vg_pool_id ( vg_pool *pool, void *item ); +u16 vg_pool_lru ( vg_pool *pool ); +void vg_pool_watch ( vg_pool *pool, u16 id ); +void vg_pool_unwatch ( vg_pool *pool, u16 id ); diff --git a/vg_mem_queue.c b/vg_mem_queue.c new file mode 100644 index 0000000..3b1a9d6 --- /dev/null +++ b/vg_mem_queue.c @@ -0,0 +1,77 @@ +#include "vg_platform.h" +#include "vg_mem.h" +#include "vg_mem_queue.h" +#include +#include + +/* + * Allocate memory on the queue. Returns NULL if allocation failed for any + * any reason. + */ +vg_queue_frame *vg_queue_alloc( vg_queue *q, u32 size ) +{ + u32 total = vg_align8(size) + sizeof(vg_queue_frame); + vg_queue_frame *frame = NULL; + + if( total > q->size ) + return NULL; + + if( q->head ) + { + u32 end = ((u8 *)q->head - q->buffer) + q->head->alloc_size, + start = ((u8 *)q->tail - q->buffer), + r0 = 0, + r1 = 0; + + if( start < end ){ + r0 = q->size-end; + r1 = start; + } + else + r0 = start - end; + + if( total < r0 ){ + frame = (vg_queue_frame *)(q->buffer + end); + } + else { + if( total < r1 ){ + q->head->alloc_size += r0; + frame = (vg_queue_frame *)q->buffer; + } + } + + if( !frame ) return NULL; + } + else{ + frame = (vg_queue_frame *)q->buffer; + q->tail = frame; + } + + memset( frame, 0, sizeof(vg_queue_frame) ); + + q->head = frame; + frame->alloc_size = total; + frame->size = size; + + return frame; +} + +/* + * Free last item from queue + */ +void vg_queue_pop( vg_queue *q ) +{ + if( q->head == q->tail ){ + q->head = NULL; + q->tail = NULL; + return; + } + + u32 start = ((u8 *)q->tail - q->buffer); + start += q->tail->alloc_size; + + if( start == q->size ) + start = 0; + + q->tail = (vg_queue_frame *)(q->buffer + start); +} diff --git a/vg_mem_queue.h b/vg_mem_queue.h index 1b556ae..f898ada 100644 --- a/vg_mem_queue.h +++ b/vg_mem_queue.h @@ -1,91 +1,21 @@ -#ifndef VG_MEM_QUEUE_H -#define VG_MEM_QUEUE_H - -#include "vg_mem.h" -#include "vg_stdint.h" +#pragma once typedef struct vg_queue vg_queue; typedef struct vg_queue_frame vg_queue_frame; -struct vg_queue_frame { +struct vg_queue_frame +{ u32 alloc_size,size; u8 data[]; }; -struct vg_queue { +struct vg_queue +{ u8 *buffer; u32 size; vg_queue_frame *head, *tail; }; -/* - * Allocate memory on the queue. Returns NULL if allocation failed for any - * any reason. - */ -static vg_queue_frame *vg_queue_alloc( vg_queue *q, u32 size ){ - u32 total = vg_align8(size) + sizeof(vg_queue_frame); - vg_queue_frame *frame = NULL; - - if( total > q->size ) - return NULL; - - if( q->head ){ - u32 end = ((u8 *)q->head - q->buffer) + q->head->alloc_size, - start = ((u8 *)q->tail - q->buffer), - r0 = 0, - r1 = 0; - - if( start < end ){ - r0 = q->size-end; - r1 = start; - } - else - r0 = start - end; - - if( total < r0 ){ - frame = (vg_queue_frame *)(q->buffer + end); - } - else { - if( total < r1 ){ - q->head->alloc_size += r0; - frame = (vg_queue_frame *)q->buffer; - } - } - - if( !frame ) return NULL; - } - else{ - frame = (vg_queue_frame *)q->buffer; - q->tail = frame; - } - - memset( frame, 0, sizeof(vg_queue_frame) ); - - q->head = frame; - frame->alloc_size = total; - frame->size = size; - - return frame; -} - -/* - * Free last item from queue - */ -static void vg_queue_pop( vg_queue *q ){ - if( q->head == q->tail ){ - q->head = NULL; - q->tail = NULL; - return; - } - - u32 start = ((u8 *)q->tail - q->buffer); - start += q->tail->alloc_size; - - if( start == q->size ) - start = 0; - - q->tail = (vg_queue_frame *)(q->buffer + start); -} - -#endif /* VG_MEM_QUEUE_H */ +vg_queue_frame *vg_queue_alloc( vg_queue *q, u32 size ); +void vg_queue_pop( vg_queue *q ); diff --git a/vg_msg.c b/vg_msg.c new file mode 100644 index 0000000..db7380a --- /dev/null +++ b/vg_msg.c @@ -0,0 +1,494 @@ +#include "vg_msg.h" +#include "vg_platform.h" + +/* write a buffer from msg, rang checked. */ +void vg_msg_wbuf( vg_msg *msg, u8 *buf, u32 len ) +{ + if( msg->error != k_vg_msg_error_OK ) return; + if( msg->cur.co+len > msg->max ){ + msg->error = k_vg_msg_error_overflow; + return; + } + + for( u32 i=0; ibuf[ msg->cur.co ++ ] = buf[i]; + } +} + +/* read a buffer from msg, rang checked. */ +void vg_msg_rbuf( vg_msg *msg, u8 *buf, u32 len ) +{ + if( msg->error != k_vg_msg_error_OK ) return; + if( msg->cur.co+len > msg->max ){ + msg->error = k_vg_msg_error_overflow; + return; + } + + for( u32 i=0; ibuf[ msg->cur.co ++ ]; + } +} + +/* write null terminated string to stream */ +void vg_msg_wstr( vg_msg *msg, const char *str ) +{ + if( msg->error != k_vg_msg_error_OK ) return; + for( u32 i=0;; i++ ){ + vg_msg_wbuf( msg, (u8[]){ str[i] }, 1 ); + if( !str[i] ) break; + } +} + +/* read null terminated string, range check and generate hash (djb2) */ +const char *vg_msg_rstr( vg_msg *msg, u32 *djb2 ) +{ + if( msg->error != k_vg_msg_error_OK ) return 0; + + u32 hash = 5381, c; + const char *str = (void *)(&msg->buf[ msg->cur.co ]); + + while( (c = msg->buf[ msg->cur.co ++ ]) ){ + if( msg->cur.co >= msg->max ){ + msg->error = k_vg_msg_error_overflow; + return 0; + } + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + + *djb2 = hash; + return str; +} + +/* begin a new frame in message stream */ +void vg_msg_frame( vg_msg *msg, const char *name ) +{ + if( msg->error != k_vg_msg_error_OK ) return; + + msg->cur.depth ++; + vg_msg_wbuf( msg, (u8[]){ k_vg_msg_frame }, 1 ); + vg_msg_wstr( msg, name ); +} + +/* end frame in message stream */ +void vg_msg_end_frame( vg_msg *msg ) +{ + if( msg->error != k_vg_msg_error_OK ) return; + if( !msg->cur.depth ){ + msg->error = k_vg_msg_error_unbalanced; + return; + } + msg->cur.depth --; + vg_msg_wbuf( msg, (u8[]){ k_vg_msg_endframe }, 1 ); +} + +/* write a KV string to stream */ +void vg_msg_wkvstr( vg_msg *msg, const char *key, const char *value ) +{ + vg_msg_wbuf( msg, (u8[]){ k_vg_msg_kvstring }, 1 ); + vg_msg_wstr( msg, key ); + vg_msg_wstr( msg, value ); +} + +/* write a binary block to stream */ +void vg_msg_wkvbin( vg_msg *msg, const char *key, u8 *bin, u32 len ) +{ + vg_msg_wbuf( msg, (u8[]){ k_vg_msg_kvbin }, 1 ); + vg_msg_wstr( msg, key ); + vg_msg_wbuf( msg, (u8 *)(&len), 4 ); + vg_msg_wbuf( msg, bin, len ); +} + +u32 vg_msg_cmd_array_count( u8 code ) +{ + return ((code & k_vg_msg_array_count_bits)>>2) + 1; +} + +u32 vg_msg_cmd_type_size( u8 code ) +{ + return 0x1 << (code & k_vg_msg_type_size_bits); +} + +/* get the byte count of a sized code */ +u32 vg_msg_cmd_bytecount( u8 code ) +{ + return vg_msg_cmd_array_count( code ) * vg_msg_cmd_type_size( code ); +} + +u8 vg_msg_count_bits( u32 count ) +{ + if( count > 16 ) vg_fatal_error( "Too large\n" ); + return ((count-1)<<2); +} + +/* write a sized type */ +void vg_msg_wkvnum( vg_msg *msg, const char *key, + u8 type, u8 count, void *data ) +{ + u8 code = type | vg_msg_count_bits(count); + + vg_msg_wbuf( msg, &code, 1 ); + vg_msg_wstr( msg, key ); + vg_msg_wbuf( msg, data, vg_msg_cmd_bytecount(code) ); +} + +void vg_msg_init( vg_msg *msg, u8 *buffer, u32 len ) +{ + msg->buf = buffer; + msg->cur.co = 0; + msg->cur.depth = 0; + msg->error = k_vg_msg_error_OK; + msg->max = len; +} + +/* + * The stream reading interface + * ----------------------------------------------------------------------------- + */ + +/* move the cursor through the next message. it will always read in the value or + * create an error if it runs of the end of the stream. every possible command + * must be handled in this function */ +int vg_msg_next( vg_msg *msg, vg_msg_cmd *cmd ) +{ + vg_msg_rbuf( msg, &cmd->code, 1 ); + if( msg->error != k_vg_msg_error_OK ) return 0; + +#ifdef VG_MSG_V1_SUPPORT + /* |sized| |count-1| |shift| + * 0 1 0 0 0 1 0 0 0x44 (1 byte, float[2]. So, never used anyway) + * converts to + * 1 0 0 0 0 0 1 0 0x82 + */ + if( cmd->code == 0x44 ) cmd->code = 0x82; +#endif + cmd->key_djb2 = 0; + if( msg->error != k_vg_msg_error_OK ) return 0; + + if( cmd->code == k_vg_msg_frame ){ + cmd->key = vg_msg_rstr( msg, &cmd->key_djb2 ); + msg->cur.depth ++; + } + else if( cmd->code == k_vg_msg_endframe ){ + if( !msg->cur.depth ){ + msg->error = k_vg_msg_error_unbalanced; + return 0; + } + msg->cur.depth --; + } + else if( cmd->code >= k_vg_msg_kv ){ + cmd->key = vg_msg_rstr( msg, &cmd->key_djb2 ); + cmd->value_djb2 = 0; + + if( cmd->code & k_vg_msg_type_base_bits ){ + u32 bytes = vg_msg_cmd_bytecount( cmd->code ); + cmd->value = &msg->buf[ msg->cur.co ]; + msg->cur.co += bytes; + } + else if( cmd->code == k_vg_msg_kvstring ){ + cmd->value = vg_msg_rstr( msg, &cmd->value_djb2 ); + } + else if( cmd->code == k_vg_msg_kvbin ){ + vg_msg_rbuf( msg, (u8 *)(&cmd->len), 4 ); + if( msg->error != k_vg_msg_error_OK ) + return 0; + cmd->value = &msg->buf[ msg->cur.co ]; + msg->cur.co += cmd->len; + } + else + msg->error = k_vg_msg_error_unhandled_cmd; + + if( msg->cur.co > msg->max ) + msg->error = k_vg_msg_error_overflow; + } + else + msg->error = k_vg_msg_error_unhandled_cmd; + + if( msg->error != k_vg_msg_error_OK ) + return 0; + else + return 1; +} + +/* move through the frame(and subframes) until we fall out of it */ +int vg_msg_skip_frame( vg_msg *msg ) +{ + vg_msg_cmd cmd; + + u32 start_depth = msg->cur.depth; + while( vg_msg_next( msg, &cmd ) ) + if( msg->cur.depth < start_depth ) + return 1; + return 0; +} + +/* + * A more friendly but slower interface + * ----------------------------------------------------------------------------- + */ + +/* moves to a frame, + * returns 0 if drops out of scope or ends. + */ +int vg_msg_seekframe( vg_msg *msg, const char *name ) +{ + vg_msg_cursor orig = msg->cur; + vg_msg_cmd cmd; + while( vg_msg_next( msg, &cmd ) ){ + if( msg->cur.depth < orig.depth ){ + msg->cur = orig; + return 0; + } + if( msg->cur.depth != orig.depth+1 ) + continue; + if( cmd.code == k_vg_msg_frame ) + if( !name || VG_STRDJB2_EQ( name, cmd.key, cmd.key_djb2 ) ) + return 1; + } + + msg->cur = orig; + return 0; +} + +/* + * Convert any type integral type to u64 + */ +u64 vg_msg_cast_to_u64( const void *src, u8 src_base, u8 src_size ) +{ + if( src_base == k_vg_msg_float ){ + if( src_size == 4 ) return (u64)(*((f32*)src)); + else if( src_size == 8 ) return (u64)(*((f64*)src)); + else return 0; + } + else { + u64 a = 0; + memcpy( &a, src, src_size ); + return a; + } +} + +/* + * Convert any integral type to i64 + */ +i64 vg_msg_cast_to_i64( const void *src, u8 src_base, u8 src_size ) +{ + if( src_base == k_vg_msg_float ){ + if( src_size == 4 ) return (i64)(*((f32*)src)); + else if( src_size == 8 ) return (i64)(*((f64*)src)); + else return 0; + } + else { + u64 a = 0; + memcpy( &a, src, src_size ); + + if( (src_base == k_vg_msg_signed) && (src_size != 8) ){ + /* extend sign bit */ + u64 sign_bit = 0x1llu << ((src_size*8)-1); + if( a & sign_bit ) + a |= (0xffffffffffffffffllu << (64-__builtin_clzll( a ))); + } + + return *((i64*)&a); + } +} + +/* + * Convert any integral type to f64 + */ +f64 vg_msg_cast_to_f64( const void *src, u8 src_base, u8 src_size ) +{ + if( src_base == k_vg_msg_float ){ + if( src_size == 4 ) return (f64)(*((f32*)src)); + else if( src_size == 8 ) return *((f64*)src); + else return 0.0; + } + else + return (f64)vg_msg_cast_to_i64( src, src_base, src_size ); +} + +/* + * Convert any full integral type code to another + * Passing in non-integral codes is undefined + */ +void vg_msg_cast( const void *src, u8 src_code, void *dst, u8 dst_code ) +{ + if( src_code == dst_code ){ + memcpy( dst, src, vg_msg_cmd_bytecount( src_code ) ); + } + else { + u32 src_n = vg_msg_cmd_array_count( src_code ), + dst_n = vg_msg_cmd_array_count( dst_code ), + src_s = vg_msg_cmd_type_size( src_code ), + dst_s = vg_msg_cmd_type_size( dst_code ), + src_b = src_code & k_vg_msg_type_base_bits, + dst_b = dst_code & k_vg_msg_type_base_bits; + + memset( dst, 0, dst_s * dst_n ); + + for( u32 i=0; icur; + while( vg_msg_next( msg, cmd ) ){ + if( msg->cur.depth < orig.depth ){ + msg->cur = orig; + return 0; + } + if( msg->cur.depth > orig.depth ) + continue; + if( cmd->code > k_vg_msg_kv ){ + if( VG_STRDJB2_EQ( key, cmd->key, cmd->key_djb2 ) ){ + msg->cur = orig; + return 1; + } + } + } + msg->error = k_vg_msg_error_OK; + msg->cur = orig; + return 0; +} + +/* + * Read a integral KV out to dst, and perform conversion if needed + * dst is always defined, if its not found its set to 0 + */ +int vg_msg_getkvintg( vg_msg *msg, const char *key, u8 type, void *dst ) +{ + vg_msg_cmd cmd; + if( vg_msg_getkvcmd( msg, key, &cmd ) ) + { + vg_msg_cast( cmd.value, cmd.code, dst, type ); + return 1; + } + else + { + memset( dst, 0, vg_msg_cmd_bytecount(type) ); + return 0; + } +} + +/* helper for reading string kvs. returns NULL if not found */ +const char *vg_msg_getkvstr( vg_msg *msg, const char *key ) +{ + vg_msg_cmd cmd; + if( vg_msg_getkvcmd( msg, key, &cmd ) ) + return cmd.value; + else + return NULL; +} + +int vg_msg_getkvvecf( vg_msg *msg, const char *key, u8 type, + void *v, void *default_value ) +{ + vg_msg_cmd cmd; + if( vg_msg_getkvcmd( msg, key, &cmd ) ) + { + vg_msg_cast( cmd.value, cmd.code, v, type ); + return 1; + } + else if( default_value ) + vg_msg_cast( default_value, type, v, type ); + + return 0; +} + + +/* debug the thing */ +void vg_msg_print( vg_msg *msg, u32 len ) +{ + if( msg->error != k_vg_msg_error_OK ){ + printf( "Message contains errors\n" ); + return; + } + + vg_msg b; + vg_msg_init( &b, msg->buf, len ); + + vg_msg_cmd cmd; + while( vg_msg_next( &b, &cmd ) ){ + if( cmd.code == k_vg_msg_frame ){ + for( u32 i=0; i (%u bytes)\n", cmd.key, cmd.len ); + else { + u32 base = cmd.code & k_vg_msg_type_base_bits, + count = vg_msg_cmd_array_count( cmd.code ), + size = vg_msg_cmd_type_size( cmd.code ); + + printf( "'%s': ", cmd.key ); + + if( count > 1 ) printf( "{ " ); + + for( u32 i=0; i 1 ) printf( " }" ); + printf( "\n" ); + } + } + } +} diff --git a/vg_msg.h b/vg_msg.h index b5fb4a4..91f0dc2 100644 --- a/vg_msg.h +++ b/vg_msg.h @@ -1,8 +1,4 @@ -#ifndef VG_MSG_H -#define VG_MSG_H -#include "vg_stdint.h" -#include "vg_platform.h" - +#pragma once /* * Example data: * kvstr "someinfo" @@ -129,17 +125,20 @@ enum vg_msg_code{ typedef struct vg_msg vg_msg; typedef struct vg_msg_cmd vg_msg_cmd; typedef struct vg_msg_cursor vg_msg_cursor; -struct vg_msg{ +struct vg_msg +{ u32 max; u8 *buf; /* reading */ - struct vg_msg_cursor { + struct vg_msg_cursor + { u32 co, depth; } cur; - enum vg_msg_error{ + enum vg_msg_error + { k_vg_msg_error_OK, k_vg_msg_error_unbalanced, k_vg_msg_error_overflow, @@ -148,7 +147,8 @@ struct vg_msg{ error; }; -struct vg_msg_cmd{ +struct vg_msg_cmd +{ u8 code; const char *key; @@ -160,519 +160,40 @@ struct vg_msg_cmd{ u32 len; /* set if binary type */ }; -/* write a buffer from msg, rang checked. */ -static void vg_msg_wbuf( vg_msg *msg, u8 *buf, u32 len ){ - if( msg->error != k_vg_msg_error_OK ) return; - if( msg->cur.co+len > msg->max ){ - msg->error = k_vg_msg_error_overflow; - return; - } - - for( u32 i=0; ibuf[ msg->cur.co ++ ] = buf[i]; - } -} - -/* read a buffer from msg, rang checked. */ -static void vg_msg_rbuf( vg_msg *msg, u8 *buf, u32 len ){ - if( msg->error != k_vg_msg_error_OK ) return; - if( msg->cur.co+len > msg->max ){ - msg->error = k_vg_msg_error_overflow; - return; - } - - for( u32 i=0; ibuf[ msg->cur.co ++ ]; - } -} - -/* write null terminated string to stream */ -static void vg_msg_wstr( vg_msg *msg, const char *str ){ - if( msg->error != k_vg_msg_error_OK ) return; - for( u32 i=0;; i++ ){ - vg_msg_wbuf( msg, (u8[]){ str[i] }, 1 ); - if( !str[i] ) break; - } -} - -/* read null terminated string, range check and generate hash (djb2) */ -static const char *vg_msg_rstr( vg_msg *msg, u32 *djb2 ){ - if( msg->error != k_vg_msg_error_OK ) return 0; - - u32 hash = 5381, c; - const char *str = (void *)(&msg->buf[ msg->cur.co ]); - - while( (c = msg->buf[ msg->cur.co ++ ]) ){ - if( msg->cur.co >= msg->max ){ - msg->error = k_vg_msg_error_overflow; - return 0; - } - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - } - - *djb2 = hash; - return str; -} - -/* begin a new frame in message stream */ -static void vg_msg_frame( vg_msg *msg, const char *name ){ - if( msg->error != k_vg_msg_error_OK ) return; - - msg->cur.depth ++; - vg_msg_wbuf( msg, (u8[]){ k_vg_msg_frame }, 1 ); - vg_msg_wstr( msg, name ); -} - -/* end frame in message stream */ -static void vg_msg_end_frame( vg_msg *msg ){ - if( msg->error != k_vg_msg_error_OK ) return; - if( !msg->cur.depth ){ - msg->error = k_vg_msg_error_unbalanced; - return; - } - msg->cur.depth --; - vg_msg_wbuf( msg, (u8[]){ k_vg_msg_endframe }, 1 ); -} - -/* write a KV string to stream */ -static void vg_msg_wkvstr( vg_msg *msg, const char *key, const char *value ){ - vg_msg_wbuf( msg, (u8[]){ k_vg_msg_kvstring }, 1 ); - vg_msg_wstr( msg, key ); - vg_msg_wstr( msg, value ); -} - -/* write a binary block to stream */ -static void vg_msg_wkvbin( vg_msg *msg, const char *key, u8 *bin, u32 len ){ - vg_msg_wbuf( msg, (u8[]){ k_vg_msg_kvbin }, 1 ); - vg_msg_wstr( msg, key ); - vg_msg_wbuf( msg, (u8 *)(&len), 4 ); - vg_msg_wbuf( msg, bin, len ); -} - -static u32 vg_msg_cmd_array_count( u8 code ){ - return ((code & k_vg_msg_array_count_bits)>>2) + 1; -} - -static u32 vg_msg_cmd_type_size( u8 code ){ - return 0x1 << (code & k_vg_msg_type_size_bits); -} - -/* get the byte count of a sized code */ -static u32 vg_msg_cmd_bytecount( u8 code ){ - return vg_msg_cmd_array_count( code ) * vg_msg_cmd_type_size( code ); -} - -static u8 vg_msg_count_bits( u32 count ){ - assert( (count <= 16) && count ); - return ((count-1)<<2); -} - -/* write a sized type */ -static void vg_msg_wkvnum( vg_msg *msg, const char *key, - u8 type, u8 count, void *data ){ - u8 code = type | vg_msg_count_bits(count); - - vg_msg_wbuf( msg, &code, 1 ); - vg_msg_wstr( msg, key ); - vg_msg_wbuf( msg, data, vg_msg_cmd_bytecount(code) ); -} - -#define _WRITE_KV_INTG_HELPER( TYPE ) \ -static void vg_msg_wkv##TYPE( vg_msg *msg, const char *key, TYPE v ){ \ - vg_msg_wkvnum( msg, key, k_vg_msg_##TYPE, 1, &v ); \ -} - -#define _WRITE_KV_VEC_HELPER( TYPE ) \ -static void vg_msg_wkv##TYPE( vg_msg *msg, const char *key, TYPE v ){ \ - vg_msg_wkvnum( msg, key, k_vg_msg_##TYPE, 1, v ); \ -} - -_WRITE_KV_INTG_HELPER( u8 ) -_WRITE_KV_INTG_HELPER( u16 ) -_WRITE_KV_INTG_HELPER( u32 ) -_WRITE_KV_INTG_HELPER( u64 ) -_WRITE_KV_INTG_HELPER( i8 ) -_WRITE_KV_INTG_HELPER( i16 ) -_WRITE_KV_INTG_HELPER( i32 ) -_WRITE_KV_INTG_HELPER( i64 ) -_WRITE_KV_INTG_HELPER( f32 ) -_WRITE_KV_INTG_HELPER( f64 ) -_WRITE_KV_VEC_HELPER( v2f ) -_WRITE_KV_VEC_HELPER( v3f ) -_WRITE_KV_VEC_HELPER( v4f ) - -static void vg_msg_init( vg_msg *msg, u8 *buffer, u32 len ){ - msg->buf = buffer; - msg->cur.co = 0; - msg->cur.depth = 0; - msg->error = k_vg_msg_error_OK; - msg->max = len; -} - -/* - * The stream reading interface - * ----------------------------------------------------------------------------- - */ - -/* move the cursor through the next message. it will always read in the value or - * create an error if it runs of the end of the stream. every possible command - * must be handled in this function */ -static int vg_msg_next( vg_msg *msg, vg_msg_cmd *cmd ){ - vg_msg_rbuf( msg, &cmd->code, 1 ); - if( msg->error != k_vg_msg_error_OK ) return 0; - -#ifdef VG_MSG_V1_SUPPORT - /* |sized| |count-1| |shift| - * 0 1 0 0 0 1 0 0 0x44 (1 byte, float[2]. So, never used anyway) - * converts to - * 1 0 0 0 0 0 1 0 0x82 - */ - if( cmd->code == 0x44 ) cmd->code = 0x82; -#endif - cmd->key_djb2 = 0; - if( msg->error != k_vg_msg_error_OK ) return 0; - - if( cmd->code == k_vg_msg_frame ){ - cmd->key = vg_msg_rstr( msg, &cmd->key_djb2 ); - msg->cur.depth ++; - } - else if( cmd->code == k_vg_msg_endframe ){ - if( !msg->cur.depth ){ - msg->error = k_vg_msg_error_unbalanced; - return 0; - } - msg->cur.depth --; - } - else if( cmd->code >= k_vg_msg_kv ){ - cmd->key = vg_msg_rstr( msg, &cmd->key_djb2 ); - cmd->value_djb2 = 0; - - if( cmd->code & k_vg_msg_type_base_bits ){ - u32 bytes = vg_msg_cmd_bytecount( cmd->code ); - cmd->value = &msg->buf[ msg->cur.co ]; - msg->cur.co += bytes; - } - else if( cmd->code == k_vg_msg_kvstring ){ - cmd->value = vg_msg_rstr( msg, &cmd->value_djb2 ); - } - else if( cmd->code == k_vg_msg_kvbin ){ - vg_msg_rbuf( msg, (u8 *)(&cmd->len), 4 ); - if( msg->error != k_vg_msg_error_OK ) - return 0; - cmd->value = &msg->buf[ msg->cur.co ]; - msg->cur.co += cmd->len; - } - else - msg->error = k_vg_msg_error_unhandled_cmd; - - if( msg->cur.co > msg->max ) - msg->error = k_vg_msg_error_overflow; - } - else - msg->error = k_vg_msg_error_unhandled_cmd; - - if( msg->error != k_vg_msg_error_OK ) - return 0; - else - return 1; -} - -/* move through the frame(and subframes) until we fall out of it */ -static int vg_msg_skip_frame( vg_msg *msg ){ - vg_msg_cmd cmd; - - u32 start_depth = msg->cur.depth; - while( vg_msg_next( msg, &cmd ) ) - if( msg->cur.depth < start_depth ) - return 1; - return 0; -} - -/* - * A more friendly but slower interface - * ----------------------------------------------------------------------------- - */ - -/* moves to a frame, - * returns 0 if drops out of scope or ends. - */ -static int vg_msg_seekframe( vg_msg *msg, const char *name ){ - vg_msg_cursor orig = msg->cur; - vg_msg_cmd cmd; - while( vg_msg_next( msg, &cmd ) ){ - if( msg->cur.depth < orig.depth ){ - msg->cur = orig; - return 0; - } - if( msg->cur.depth != orig.depth+1 ) - continue; - if( cmd.code == k_vg_msg_frame ) - if( !name || VG_STRDJB2_EQ( name, cmd.key, cmd.key_djb2 ) ) - return 1; - } - - msg->cur = orig; - return 0; -} - -/* - * Convert any type integral type to u64 - */ -static u64 vg_msg_cast_to_u64( const void *src, u8 src_base, u8 src_size ){ - if( src_base == k_vg_msg_float ){ - if( src_size == 4 ) return (u64)(*((f32*)src)); - else if( src_size == 8 ) return (u64)(*((f64*)src)); - else return 0; - } - else { - u64 a = 0; - memcpy( &a, src, src_size ); - return a; - } -} - -/* - * Convert any integral type to i64 - */ -static i64 vg_msg_cast_to_i64( const void *src, u8 src_base, u8 src_size ){ - if( src_base == k_vg_msg_float ){ - if( src_size == 4 ) return (i64)(*((f32*)src)); - else if( src_size == 8 ) return (i64)(*((f64*)src)); - else return 0; - } - else { - u64 a = 0; - memcpy( &a, src, src_size ); - - if( (src_base == k_vg_msg_signed) && (src_size != 8) ){ - /* extend sign bit */ - u64 sign_bit = 0x1llu << ((src_size*8)-1); - if( a & sign_bit ) - a |= (0xffffffffffffffffllu << (64-__builtin_clzll( a ))); - } - - return *((i64*)&a); - } -} - -/* - * Convert any integral type to f64 - */ -static f64 vg_msg_cast_to_f64( const void *src, u8 src_base, u8 src_size ){ - if( src_base == k_vg_msg_float ){ - if( src_size == 4 ) return (f64)(*((f32*)src)); - else if( src_size == 8 ) return *((f64*)src); - else return 0.0; - } - else - return (f64)vg_msg_cast_to_i64( src, src_base, src_size ); -} - -/* - * Convert any full integral type code to another - * Passing in non-integral codes is undefined - */ -static void vg_msg_cast( const void *src, u8 src_code, void *dst, u8 dst_code ){ - if( src_code == dst_code ){ - memcpy( dst, src, vg_msg_cmd_bytecount( src_code ) ); - } - else { - u32 src_n = vg_msg_cmd_array_count( src_code ), - dst_n = vg_msg_cmd_array_count( dst_code ), - src_s = vg_msg_cmd_type_size( src_code ), - dst_s = vg_msg_cmd_type_size( dst_code ), - src_b = src_code & k_vg_msg_type_base_bits, - dst_b = dst_code & k_vg_msg_type_base_bits; - - memset( dst, 0, dst_s * dst_n ); - - for( u32 i=0; icur; - while( vg_msg_next( msg, cmd ) ){ - if( msg->cur.depth < orig.depth ){ - msg->cur = orig; - return 0; - } - if( msg->cur.depth > orig.depth ) - continue; - if( cmd->code > k_vg_msg_kv ){ - if( VG_STRDJB2_EQ( key, cmd->key, cmd->key_djb2 ) ){ - msg->cur = orig; - return 1; - } - } - } - msg->error = k_vg_msg_error_OK; - msg->cur = orig; - return 0; -} +void vg_msg_wbuf( vg_msg *msg, u8 *buf, u32 len ); +void vg_msg_rbuf( vg_msg *msg, u8 *buf, u32 len ); +void vg_msg_wstr( vg_msg *msg, const char *str ); +const char *vg_msg_rstr( vg_msg *msg, u32 *djb2 ); +void vg_msg_frame( vg_msg *msg, const char *name ); +void vg_msg_end_frame( vg_msg *msg ); +void vg_msg_wkvstr( vg_msg *msg, const char *key, const char *value ); +void vg_msg_wkvbin( vg_msg *msg, const char *key, u8 *bin, u32 len ); +void vg_msg_wkvnum( vg_msg *msg, const char *key, + u8 type, u8 count, void *data ); +u32 vg_msg_cmd_array_count( u8 code ); +u32 vg_msg_cmd_type_size( u8 code ); +u32 vg_msg_cmd_bytecount( u8 code ); +u8 vg_msg_count_bits( u32 count ); + +void vg_msg_init( vg_msg *msg, u8 *buffer, u32 len ); +int vg_msg_next( vg_msg *msg, vg_msg_cmd *cmd ); +int vg_msg_skip_frame( vg_msg *msg ); +int vg_msg_seekframe( vg_msg *msg, const char *name ); +u64 vg_msg_cast_to_u64( const void *src, u8 src_base, u8 src_size ); +i64 vg_msg_cast_to_i64( const void *src, u8 src_base, u8 src_size ); +f64 vg_msg_cast_to_f64( const void *src, u8 src_base, u8 src_size ); +void vg_msg_cast( const void *src, u8 src_code, void *dst, u8 dst_code ); + +int vg_msg_getkvcmd( vg_msg *msg, const char *key, vg_msg_cmd *cmd ); /* * Read a integral KV out to dst, and perform conversion if needed * dst is always defined, if its not found its set to 0 */ -static int vg_msg_getkvintg( vg_msg *msg, const char *key, u8 type, void *dst ){ - vg_msg_cmd cmd; - if( vg_msg_getkvcmd( msg, key, &cmd ) ){ - vg_msg_cast( cmd.value, cmd.code, dst, type ); - return 1; - } - else { - memset( dst, 0, vg_msg_cmd_bytecount(type) ); - return 0; - } -} +int vg_msg_getkvintg( vg_msg *msg, const char *key, u8 type, void *dst ); /* helper for reading string kvs. returns NULL if not found */ -static const char *vg_msg_getkvstr( vg_msg *msg, const char *key ){ - vg_msg_cmd cmd; - if( vg_msg_getkvcmd( msg, key, &cmd ) ) - return cmd.value; - else - return NULL; -} - -#define _GET_KV_INTG_HELPER( TYPE ) \ -static TYPE vg_msg_getkv##TYPE( vg_msg *msg, const char *key, \ - TYPE default_value ){ \ - vg_msg_cmd cmd; \ - if( vg_msg_getkvcmd( msg, key, &cmd ) ){ \ - TYPE v; \ - vg_msg_cast( cmd.value, cmd.code, &v, k_vg_msg_##TYPE ); \ - return v; \ - } \ - else \ - return default_value; \ -} - -#define _GET_KV_VEC_HELPER( TYPE ) \ -static int vg_msg_getkv##TYPE( vg_msg *msg, const char *key, \ - TYPE v, \ - TYPE default_value ){ \ - vg_msg_cmd cmd; \ - if( vg_msg_getkvcmd( msg, key, &cmd ) ){ \ - vg_msg_cast( cmd.value, cmd.code, v, k_vg_msg_##TYPE ); \ - return 1; \ - } \ - else \ - if ( default_value ) \ - vg_msg_cast( default_value, k_vg_msg_##TYPE, v, k_vg_msg_##TYPE ); \ - return 0; \ -} - -_GET_KV_INTG_HELPER( u8 ) -_GET_KV_INTG_HELPER( u16 ) -_GET_KV_INTG_HELPER( u32 ) -_GET_KV_INTG_HELPER( u64 ) -_GET_KV_INTG_HELPER( i8 ) -_GET_KV_INTG_HELPER( i16 ) -_GET_KV_INTG_HELPER( i32 ) -_GET_KV_INTG_HELPER( i64 ) -_GET_KV_INTG_HELPER( f32 ) -_GET_KV_INTG_HELPER( f64 ) -_GET_KV_VEC_HELPER( v2f ) -_GET_KV_VEC_HELPER( v3f ) -_GET_KV_VEC_HELPER( v4f ) - -/* debug the thing */ -static void vg_msg_print( vg_msg *msg, u32 len ){ - if( msg->error != k_vg_msg_error_OK ){ - printf( "Message contains errors\n" ); - return; - } - - vg_msg b; - vg_msg_init( &b, msg->buf, len ); - - vg_msg_cmd cmd; - while( vg_msg_next( &b, &cmd ) ){ - if( cmd.code == k_vg_msg_frame ){ - for( u32 i=0; i (%u bytes)\n", cmd.key, cmd.len ); - else { - u32 base = cmd.code & k_vg_msg_type_base_bits, - count = vg_msg_cmd_array_count( cmd.code ), - size = vg_msg_cmd_type_size( cmd.code ); - - printf( "'%s': ", cmd.key ); - - if( count > 1 ) printf( "{ " ); - - for( u32 i=0; i 1 ) printf( " }" ); - printf( "\n" ); - } - } - } -} - -#endif /* VG_MSG_H */ +const char *vg_msg_getkvstr( vg_msg *msg, const char *key ); +int vg_msg_getkvvecf( vg_msg *msg, const char *key, u8 type, + void *v, void *default_value ); +void vg_msg_print( vg_msg *msg, u32 len ); diff --git a/vg_opt.c b/vg_opt.c new file mode 100644 index 0000000..ba0f9c4 --- /dev/null +++ b/vg_opt.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ + +#include "vg_opt.h" +#include "vg_platform.h" +#include "vg_log.h" +#include + +/* + * Supported: + * short flags | -abc + * short options | -a value + * multi-set options | -ab value + * + * long gnu options | --long-value=test + * standard agument | regular_thing + */ + +static int vg_argi = 1; +static int vg_argj = 1; +static int vg_argc = 0; +static int vg_consume_next = 0; +static char **vg_argv; + +/* Will return 0 if exhausted */ +int vg_argp( int argc, char *argv[] ) +{ + vg_argv = argv; + vg_argc = argc; + + static int delta_i = 0; + static int delta_j = 0; + + if( vg_argj != 1 && !vg_argv[ vg_argi ][ vg_argj ] ) + { + vg_argj = 1; + vg_argi ++; + } + + if( vg_consume_next ) + { + vg_consume_next = 0; + vg_argi ++; + } + + if( vg_argi >= argc ) + return 0; + + if( (delta_i == vg_argi) && (delta_j == vg_argj) ) + { + char *cur = &vg_argv[ vg_argi ][ vg_argj ]; + + if( *cur != '-' ) + { + vg_error( "Unknown opt '-%c'\n", *cur ); + } + else + { + vg_error( "Unknown opt '--%s'\n", cur + 1 ); + } + + exit(0); + } + + delta_i = vg_argi; + delta_j = vg_argj; + + return 1; +} + +/* Example: see if -c is set */ +int vg_opt( char c ) +{ + char *carg = vg_argv[ vg_argi ]; + + if( carg[0] == '-' ) + { + if( carg[1] == '-' ) + return 0; + + if( carg[ vg_argj ] == c ) + { + vg_argj ++; + + return 1; + } + } + + return 0; +} + +/* Example: get -c *value* */ +char *vg_opt_arg( char c ) +{ + if( vg_opt( c ) ) + { + if( vg_argi < vg_argc-1 ) + { + if( vg_argv[ vg_argi + 1 ][0] != '-' ) + { + vg_consume_next = 1; + return vg_argv[ vg_argi + 1 ]; + } + } + + vg_error( "Option '%c' requires argument!\n", c ); + exit(0); + } + + return NULL; +} + +/* Example see if --big is set */ +int vg_long_opt( char *name ) +{ + char *carg = vg_argv[ vg_argi ]; + + if( carg[0] == '-' ) + { + if( carg[1] == '-' ) + { + if( !strcmp( name, carg+2 ) ) + { + vg_consume_next = 1; + return 1; + } + } + } + + return 0; +} + +/* Example: get --big=value */ +char *vg_long_opt_arg( char *name ) +{ + char *carg = vg_argv[ vg_argi ]; + + if( carg[0] == '-' ) + { + if( carg[1] == '-' ) + { + int k = 2; int set = 0; + while( carg[ k ] ) + { + if( carg[ k ] == '=' ) + { + set = 1; + break; + } + + k ++; + } + + if( !strncmp( name, carg+2, k-2 ) ) + { + vg_consume_next = 1; + + // the rest + if( set ) + { + return carg + k + 1; + } + else + { + vg_error( "Long option '%s' requires argument\n", name ); + } + } + } + } + + return NULL; +} + +/* Example: get regular_thing */ +char *vg_arg(void) +{ + char *carg = vg_argv[ vg_argi ]; + + if( carg[0] != '-' ) + { + vg_consume_next = 1; + return carg; + } + + return NULL; +} diff --git a/vg_opt.h b/vg_opt.h index c817d06..6f2d879 100644 --- a/vg_opt.h +++ b/vg_opt.h @@ -1,191 +1,23 @@ /* - * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved + * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved */ -#ifndef VG_OPT_H -#define VG_OPT_H - -#include -#include "vg_platform.h" -#include "vg_log.h" - -/* - * Supported: - * short flags | -abc - * short options | -a value - * multi-set options | -ab value - * - * long gnu options | --long-value=test - * standard agument | regular_thing - */ - -static int vg_argi = 1; -static int vg_argj = 1; -static int vg_argc = 0; -static int vg_consume_next = 0; -static char **vg_argv; +#pragma once /* Will return 0 if exhausted */ -int vg_argp( int argc, char *argv[] ) -{ - vg_argv = argv; - vg_argc = argc; - - static int delta_i = 0; - static int delta_j = 0; - - if( vg_argj != 1 && !vg_argv[ vg_argi ][ vg_argj ] ) - { - vg_argj = 1; - vg_argi ++; - } - - if( vg_consume_next ) - { - vg_consume_next = 0; - vg_argi ++; - } - - if( vg_argi >= argc ) - return 0; - - if( (delta_i == vg_argi) && (delta_j == vg_argj) ) - { - char *cur = &vg_argv[ vg_argi ][ vg_argj ]; - - if( *cur != '-' ) - { - vg_error( "Unknown opt '-%c'\n", *cur ); - } - else - { - vg_error( "Unknown opt '--%s'\n", cur + 1 ); - } - - exit(0); - } - - delta_i = vg_argi; - delta_j = vg_argj; - - return 1; -} +int vg_argp( int argc, char *argv[] ); /* Example: see if -c is set */ -int vg_opt( char c ) -{ - char *carg = vg_argv[ vg_argi ]; - - if( carg[0] == '-' ) - { - if( carg[1] == '-' ) - return 0; - - if( carg[ vg_argj ] == c ) - { - vg_argj ++; - - return 1; - } - } - - return 0; -} +int vg_opt( char c ); /* Example: get -c *value* */ -char *vg_opt_arg( char c ) -{ - if( vg_opt( c ) ) - { - if( vg_argi < vg_argc-1 ) - { - if( vg_argv[ vg_argi + 1 ][0] != '-' ) - { - vg_consume_next = 1; - return vg_argv[ vg_argi + 1 ]; - } - } - - vg_error( "Option '%c' requires argument!\n", c ); - exit(0); - } - - return NULL; -} +char *vg_opt_arg( char c ); /* Example see if --big is set */ -int vg_long_opt( char *name ) -{ - char *carg = vg_argv[ vg_argi ]; - - if( carg[0] == '-' ) - { - if( carg[1] == '-' ) - { - if( !strcmp( name, carg+2 ) ) - { - vg_consume_next = 1; - return 1; - } - } - } - - return 0; -} +int vg_long_opt( char *name ); /* Example: get --big=value */ -char *vg_long_opt_arg( char *name ) -{ - char *carg = vg_argv[ vg_argi ]; - - if( carg[0] == '-' ) - { - if( carg[1] == '-' ) - { - int k = 2; int set = 0; - while( carg[ k ] ) - { - if( carg[ k ] == '=' ) - { - set = 1; - break; - } - - k ++; - } - - if( !strncmp( name, carg+2, k-2 ) ) - { - vg_consume_next = 1; - - // the rest - if( set ) - { - return carg + k + 1; - } - else - { - vg_error( "Long option '%s' requires argument\n", name ); - } - } - } - } - - return NULL; -} +char *vg_long_opt_arg( char *name ); /* Example: get regular_thing */ -char *vg_arg(void) -{ - char *carg = vg_argv[ vg_argi ]; - - if( carg[0] != '-' ) - { - vg_consume_next = 1; - return carg; - } - - return NULL; -} - -#endif +char *vg_arg(void); diff --git a/vg_perlin.c b/vg_perlin.c new file mode 100644 index 0000000..6b8b087 --- /dev/null +++ b/vg_perlin.c @@ -0,0 +1,152 @@ +#include "vg_m.h" +#include "vg_perlin.h" + +static int perlin_hash[] = { +0x46,0xD5,0xB8,0xD3,0xF2,0xE5,0xCC,0x07,0xD0,0xB3,0x7A,0xA2,0xC3,0xDA,0xDC,0x7F, +0xE0,0xB7,0x42,0xA0,0xBF,0x41,0x92,0x32,0x6F,0x0D,0x45,0xC7,0x54,0xDB,0x30,0xC2, +0xD5,0xDA,0x55,0x09,0xDE,0x74,0x48,0x20,0xE1,0x24,0x5C,0x4D,0x6F,0x36,0xD8,0xE9, +0x8D,0x8F,0x54,0x99,0x98,0x51,0xFE,0xDB,0x26,0x04,0x65,0x57,0x56,0xF3,0x53,0x30, +0x3D,0x16,0xC0,0xB6,0xF2,0x47,0xCF,0x62,0xB0,0x6C,0x8F,0x4F,0x8C,0x4C,0x17,0xF0, +0x19,0x7E,0x2D,0x81,0x8D,0xFB,0x10,0xD3,0x49,0x50,0x60,0xFD,0x38,0x15,0x3B,0xEE, +0x05,0xC1,0xCF,0x62,0x97,0x75,0xDF,0x4E,0x4D,0x89,0x5E,0x88,0x5C,0x30,0x8C,0x54, +0x1D,0x39,0x41,0xEA,0xA2,0x63,0x12,0x1B,0x8E,0x35,0x22,0x9B,0x98,0xA3,0x7F,0x80, +0xD6,0x27,0x94,0x66,0xB5,0x1D,0x7E,0xDF,0x96,0x28,0x38,0x3A,0xA0,0xE8,0x71,0x09, +0x62,0x5E,0x9D,0x53,0x58,0x1B,0x7D,0x0D,0x2D,0x99,0x77,0x83,0xC3,0x89,0xC2,0xA2, +0xA7,0x1D,0x78,0x80,0x37,0xC1,0x87,0xFF,0x65,0xBF,0x2C,0xF1,0xE5,0xB3,0x09,0xE0, +0x25,0x92,0x83,0x0F,0x8A,0x57,0x3C,0x0B,0xC6,0xBC,0x44,0x16,0xE3,0xCE,0xC3,0x0D, +0x69,0xD3,0xC6,0x99,0xB8,0x46,0x44,0xC4,0xF3,0x1E,0xBF,0xF5,0xB4,0xDB,0xFB,0x93, +0xA1,0x7B,0xC9,0x08,0x77,0x22,0xE5,0x02,0xEF,0x9E,0x90,0x94,0x8A,0xA6,0x3D,0x7E, +0xA2,0xA0,0x10,0x82,0x47,0x5C,0xAA,0xF8,0x2F,0x0D,0x9F,0x76,0xDA,0x99,0x0F,0xCB, +0xE2,0x02,0x0C,0x75,0xCA,0x35,0x29,0xA6,0x49,0x83,0x6D,0x91,0xB4,0xEC,0x31,0x69, +0xBA,0x13,0xF3,0xC7,0x21,0x06,0xC8,0x79,0xEF,0xB1,0x9C,0x6A,0xEE,0x64,0x9A,0xDC, +0x1E,0xC6,0x18,0x93,0xA9,0x7E,0x89,0x7D,0x96,0xE5,0x44,0xB8,0x00,0x15,0xAF,0x8C, +0x78,0x8F,0xA8,0x05,0xA7,0x07,0x25,0x9A,0xC8,0x5D,0x90,0x1A,0x41,0x53,0x30,0xD3, +0x24,0x33,0x71,0xB4,0x50,0x6E,0xE4,0xEA,0x0D,0x2B,0x6D,0xF5,0x17,0x08,0x74,0x49, +0x71,0xC2,0xAC,0xF7,0xDC,0xB2,0x7E,0xCC,0xB6,0x1B,0xB8,0xA9,0x52,0xCF,0x6B,0x51, +0xD2,0x4E,0xC9,0x43,0xEE,0x2E,0x92,0x24,0xBB,0x47,0x4D,0x0C,0x3E,0x21,0x53,0x19, +0xD4,0x82,0xE2,0xC6,0x93,0x85,0x0A,0xF8,0xFA,0x04,0x07,0xD3,0x1D,0xEC,0x03,0x66, +0xFD,0xB1,0xFB,0x8F,0xC5,0xDE,0xE8,0x29,0xDF,0x23,0x09,0x9D,0x7C,0x43,0x3D,0x4D, +0x89,0xB9,0x6F,0xB4,0x6B,0x4A,0x51,0xC3,0x94,0xF4,0x7C,0x5E,0x19,0x87,0x79,0xC1, +0x80,0x0C,0x45,0x12,0xEC,0x95,0xF3,0x31,0x68,0x42,0xE1,0x06,0x57,0x0E,0xA7,0xFB, +0x78,0x96,0x87,0x23,0xA5,0x20,0x7A,0x09,0x3A,0x45,0xE6,0xD9,0x5E,0x6A,0xD6,0xAA, +0x29,0x50,0x92,0x4E,0xD0,0xB5,0x91,0xC2,0x9A,0xCF,0x07,0xFE,0xB2,0x15,0xEB,0xE4, +0x84,0x40,0x14,0x47,0xFA,0x93,0xB9,0x06,0x69,0xDB,0xBD,0x4E,0xEA,0x52,0x9B,0xDE, +0x5B,0x50,0x36,0xAB,0xB3,0x1F,0xD2,0xCD,0x9C,0x13,0x07,0x7E,0x8B,0xED,0x72,0x62, +0x74,0x77,0x3B,0x88,0xAC,0x5B,0x6A,0xBC,0xDA,0x99,0xE8,0x24,0x90,0x5A,0xCA,0x8D, +0x5C,0x2B,0xF8,0xF1,0xE1,0x1D,0x94,0x11,0xEA,0xCC,0x02,0x09,0x1E,0xA2,0x48,0x67, +0x87,0x5A,0x7E,0xC6,0xCC,0xA3,0xFB,0xC5,0x36,0xEB,0x5C,0xE1,0xAF,0x1E,0xBE,0xE7, +0xD8,0x8F,0x70,0xAE,0x42,0x05,0xF5,0xCD,0x2D,0xA2,0xB0,0xFD,0xEF,0x65,0x2C,0x22, +0xCB,0x8C,0x8B,0xAA,0x3D,0x86,0xE2,0xCD,0xBE,0xC3,0x42,0x38,0xE3,0x9C,0x08,0xB5, +0xAE,0xBD,0x54,0x73,0x83,0x70,0x24,0x47,0xCA,0x4C,0x04,0xC4,0xE0,0x1D,0x40,0xED, +0xF4,0x2B,0x50,0x8E,0x97,0xB3,0xF0,0xA6,0x76,0xDB,0x49,0x30,0xE5,0xD9,0x71,0x07, +0xB2,0xF1,0x0F,0xD6,0x77,0xAA,0x72,0xC0,0xAF,0x66,0xD8,0x40,0xC6,0x08,0x19,0x8C, +0xD9,0x8F,0x5A,0x75,0xAC,0xBE,0xC2,0x40,0x5B,0xBD,0x0D,0x1D,0x00,0xAF,0x26,0x5E, +0x78,0x43,0xAA,0xC6,0x4F,0xF3,0xD8,0xE2,0x7F,0x0C,0x1E,0x77,0x4D,0x35,0x96,0x23, +0x32,0x44,0x03,0x8D,0x92,0xE7,0xFD,0x48,0x07,0xD0,0x58,0xFC,0x6D,0xC9,0x91,0x33, +0xF0,0x23,0x45,0xA4,0x29,0xB9,0xF5,0xB0,0x68,0x8F,0x7B,0x59,0x15,0x8E,0xA6,0x66, +0x15,0xA0,0x76,0x9B,0x69,0xCB,0x38,0xA5,0xF4,0xB4,0x6B,0xDC,0x1F,0xAB,0xAE,0x12, +0x77,0xC0,0x8C,0x4A,0x03,0xB9,0x73,0xD3,0x6D,0x52,0xC5,0xF5,0x6E,0x4E,0x4B,0xA3, +0x24,0x02,0x58,0xEE,0x5F,0xF9,0xD6,0xD0,0x1D,0xBC,0xF4,0xB8,0x4F,0xFD,0x4B,0x2D, +0x34,0x77,0x46,0xE5,0xD4,0x33,0x7B,0x9C,0x35,0xCD,0xB0,0x5D,0x06,0x39,0x99,0xEB, +0x0C,0xD0,0x0F,0xF7,0x92,0xB5,0x58,0x5B,0x5E,0x79,0x12,0xF4,0x05,0xF6,0x21,0x07, +0x0B,0x49,0x1A,0xFB,0xD4,0x98,0xC4,0xEF,0x7A,0xD6,0xCA,0xA1,0xDA,0xB3,0x51,0x00, +0x76,0xEC,0x08,0x48,0x40,0x35,0xD7,0x94,0xBE,0xF5,0x7B,0xA4,0x20,0x81,0x5F,0x82, +0xF3,0x6F,0x96,0x24,0x98,0xB6,0x49,0x18,0xC8,0xC5,0x8C,0xD2,0x38,0x7F,0xC4,0xF6, +0xAA,0x87,0xDC,0x73,0x5B,0xA1,0xAF,0xE5,0x3D,0x37,0x6B,0x85,0xED,0x38,0x62,0x7D, +0x57,0xBD,0xCF,0xB5,0x1B,0xA8,0xBB,0x32,0x33,0xD3,0x34,0x5A,0xC1,0x5D,0xFB,0x28, +0x6E,0xE1,0x67,0x51,0xBB,0x31,0x92,0x83,0xAC,0xAA,0x72,0x52,0xFD,0x13,0x4F,0x73, +0xD3,0xF0,0x5E,0xFC,0xBA,0xB1,0x3C,0x7B,0x08,0x76,0x03,0x38,0x1E,0xD1,0xCC,0x33, +0xA3,0x1E,0xFC,0xE0,0x82,0x30,0x27,0x93,0x71,0x35,0x75,0x77,0xBA,0x78,0x10,0x33, +0xCD,0xAB,0xCF,0x8E,0xAD,0xF9,0x32,0xC9,0x15,0x9F,0xD6,0x6D,0xA8,0xAE,0xB1,0x3F, +0x90,0xEB,0xD4,0xF9,0x31,0x81,0xA3,0x53,0x99,0x4B,0x3C,0x93,0x3B,0xFE,0x55,0xFF, +0x25,0x9F,0xCC,0x07,0xC5,0x2C,0x14,0xA7,0xA4,0x1E,0x6C,0xB6,0x91,0x2A,0xE0,0x3E, +0x7F,0x39,0x0A,0xD9,0x24,0x3C,0x01,0xA0,0x30,0x99,0x8E,0xB8,0x1D,0xF9,0xA7,0x78, +0x86,0x95,0x35,0x0E,0x21,0xDA,0x7A,0x7B,0xAD,0x9F,0x4E,0xF6,0x63,0x5B,0x96,0xBB, +0x87,0x36,0x3F,0xA7,0x1A,0x66,0x91,0xCD,0xB0,0x3B,0xC0,0x4F,0x54,0xD2,0x5F,0xBB, +0x38,0x89,0x1C,0x79,0x7E,0xA2,0x02,0xE4,0x80,0x84,0x1E,0x33,0xAB,0x74,0xFA,0xBE, +0x31,0x46,0x2E,0xC5,0x15,0xB9,0x12,0xE9,0xD3,0x73,0x43,0xEA,0x74,0x11,0xA7,0xC0, +0xD5,0xD8,0x39,0x08,0x9F,0x4F,0xC7,0x71,0x25,0x09,0x51,0x65,0xD6,0xA8,0x02,0x1F +}; + +// Note: Hash must be power of 2! +#define PERLIN_HASH_LENGTH 1024 +#define PERLIN_HASH_MASK (PERLIN_HASH_LENGTH-1) + +static int perlin_noise2( int x, int y, int seed ) +{ + return perlin_hash[ (perlin_hash[(y+seed) & PERLIN_HASH_MASK] + x) + & PERLIN_HASH_MASK ]; +} + +static int perlin_noise1( int i, int seed ) +{ + return perlin_hash[ (seed + i) & PERLIN_HASH_MASK ]; +} + +static f32 perlin_smooth( f32 x, f32 y, f32 t ) +{ + return vg_lerpf( x, y, t*t*(3.0f-2.0f*t) ); +} + +f32 vg_perlin_noise_2d( f32 x, f32 y, int seed ) +{ + int ix = x, iy = y; + f32 x_frac = x - ix, + y_frac = y - iy; + + int s = perlin_noise2( ix, iy, seed ), + t = perlin_noise2( ix+1, iy, seed ), + u = perlin_noise2( ix, iy+1, seed ), + v = perlin_noise2( ix+1, iy+1, seed ); + + f32 low = perlin_smooth( s,t,x_frac ), + high = perlin_smooth( u,v,x_frac ); + + return perlin_smooth( low, high, y_frac ); +} + +f32 vg_perlin_noise_1d( f32 v, int seed ) +{ + int iv = v; + f32 frac = v-iv; + int s = perlin_noise1( iv, seed ), + t = perlin_noise1( iv+1, seed ); + + return perlin_smooth( s, t, frac ); +} + +f32 vg_perlin_fract_1d( f32 v, f32 freq, int octaves, int seed ) +{ + f32 xa = v*freq, + amp = 1.0f, + fin = 0.f, + div = 0.f; + + for( int i=0; i #include #include @@ -45,60 +27,39 @@ struct vg_achievement #include #include #include +#endif -#include "vg_string.h" - -enum strncpy_behaviour{ - k_strncpy_always_add_null = 0, - k_strncpy_allow_cutoff = 1, - k_strncpy_overflow_fatal = 2 -}; - -static void vg_fatal_error( const char *fmt, ... ); -static u32 vg_strncpy( const char *src, char *dst, u32 len, - enum strncpy_behaviour behaviour ) -{ - for( u32 i=0; i +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; +typedef float f32; +typedef double f64; +typedef uint8_t bool; -#define VG_STRDJB2_EQ( CS1, S2, H2 ) \ - vg_strdjb2_eq( CS1, vg_strdjb2(CS1), S2, H2 ) +typedef unsigned int uint; +typedef int v2i[2]; +typedef int v3i[3]; +typedef int v4i[4]; +typedef float v2f[2]; +typedef float v3f[3]; +typedef float v4f[4]; +typedef v2f m2x2f[2]; +typedef v3f m3x3f[3]; +typedef v3f m4x3f[4]; +typedef v4f m4x4f[4]; +typedef v3f boxf[2]; +/* anything compiled against VG shall implement this function somewhere. */ +void vg_fatal_error( const char *fmt, ... ); #define VG_MIN( A, B ) ((A)<(B)?(A):(B)) #define VG_MAX( A, B ) ((A)>(B)?(A):(B)) -#endif +#define vg_list_size( A ) (sizeof(A)/sizeof(A[0])) diff --git a/vg_profiler.c b/vg_profiler.c new file mode 100644 index 0000000..d203c92 --- /dev/null +++ b/vg_profiler.c @@ -0,0 +1,116 @@ +#include "vg_platform.h" +#include "vg_profiler.h" +#include "vg_engine.h" +#include "vg_imgui.h" + +int vg_profiler = 0; + +void vg_profile_begin( struct vg_profile *profile ) +{ + profile->start = SDL_GetPerformanceCounter(); +} + +void vg_profile_increment( struct vg_profile *profile ) +{ + profile->buffer_current ++; + + if( profile->buffer_count < VG_PROFILE_SAMPLE_COUNT ) + profile->buffer_count ++; + + if( profile->buffer_current >= VG_PROFILE_SAMPLE_COUNT ) + profile->buffer_current = 0; + + profile->samples[ profile->buffer_current ] = 0; +} + +void vg_profile_end( struct vg_profile *profile ) +{ + u64 time_end = SDL_GetPerformanceCounter(), + delta = time_end - profile->start; + + if( profile->mode == k_profile_mode_frame ){ + profile->samples[ profile->buffer_current ] = delta; + vg_profile_increment( profile ); + } + else{ + profile->samples[ profile->buffer_current ] += delta; + } +} + +void vg_profile_drawn( struct vg_profile **profiles, u32 count, + f64 budget, ui_rect panel, + int dir, i32 normalize ) +{ + if( panel[2] == 0 ) + panel[2] = 256; + + if( panel[3] == 0 ) + panel[3] = VG_PROFILE_SAMPLE_COUNT * 2; + + f64 sh = (f32)panel[3^dir] / (f32)VG_PROFILE_SAMPLE_COUNT, + sw = (f32)panel[2^dir]; + + ui_fill( panel, 0xa0000000 ); + + if( count > 8 ) vg_fatal_error( "Too many profiles\n" ); + + f64 avgs[8]; + u32 colours[8]; + for( u32 i=0; isamples[i] * rate_mul; + } + + for( int j=0; jsamples[i] * rate_mul, + px = (total / budget) * sw, + wx = (sample / budget) * sw; + + ui_rect block; + block[0^dir] = panel[0^dir] + px; + block[1^dir] = panel[1^dir] + (f32)i*sh; + block[2^dir] = VG_MAX( 1, wx-1 ); + block[3^dir] = ceilf(sh)-1; + ui_fill( block, colours[j] ); + + total += sample; + avgs[j] += sample; + } + } + + char infbuf[64]; + + snprintf( infbuf, 64, "accuracy: %.7fms", rate_mul ); + ui_text( (ui_rect){ panel[0] + 4, + panel[1] + panel[3] - 14, 500, 30 }, + infbuf, + 1, + k_ui_align_left, 0 ); + + for( int i=0; iname ); + + ui_text( (ui_rect){ panel[0] + 4, + panel[1] + panel[3] + 4 + i*14, + panel[2]-8, 14 }, + infbuf, 1, k_ui_align_left, 0 ); + } +} + +void vg_profiler_init(void) +{ + VG_VAR_I32( vg_profiler, flags=VG_VAR_PERSISTENT ); +} diff --git a/vg_profiler.h b/vg_profiler.h index 699e2a7..74190cb 100644 --- a/vg_profiler.h +++ b/vg_profiler.h @@ -1,13 +1,9 @@ -#ifndef VG_PROFILER_H -#define VG_PROFILER_H - -#define VG_GAME -#include "vg.h" +#pragma once #include "vg_platform.h" - +#include "vg_imgui.h" #define VG_PROFILE_SAMPLE_COUNT 128 -static int vg_profiler = 0; +extern int vg_profiler; struct vg_profile { @@ -26,113 +22,10 @@ struct vg_profile u64 start; }; -static void vg_profile_begin( struct vg_profile *profile ) -{ - profile->start = SDL_GetPerformanceCounter(); -} - -static void vg_profile_increment( struct vg_profile *profile ) -{ - profile->buffer_current ++; - - if( profile->buffer_count < VG_PROFILE_SAMPLE_COUNT ) - profile->buffer_count ++; - - if( profile->buffer_current >= VG_PROFILE_SAMPLE_COUNT ) - profile->buffer_current = 0; - - profile->samples[ profile->buffer_current ] = 0; -} - -static void vg_profile_end( struct vg_profile *profile ) -{ - u64 time_end = SDL_GetPerformanceCounter(), - delta = time_end - profile->start; - - if( profile->mode == k_profile_mode_frame ){ - profile->samples[ profile->buffer_current ] = delta; - vg_profile_increment( profile ); - } - else{ - profile->samples[ profile->buffer_current ] += delta; - } -} - -static void vg_profile_drawn( struct vg_profile **profiles, u32 count, - f64 budget, ui_rect panel, - int dir, i32 normalize ) -{ - if( panel[2] == 0 ) - panel[2] = 256; - - if( panel[3] == 0 ) - panel[3] = VG_PROFILE_SAMPLE_COUNT * 2; - - f64 sh = (f32)panel[3^dir] / (f32)VG_PROFILE_SAMPLE_COUNT, - sw = (f32)panel[2^dir]; - - ui_fill( panel, 0xa0000000 ); - - assert( count <= 8 ); - f64 avgs[8]; - u32 colours[8]; - for( u32 i=0; isamples[i] * rate_mul; - } - - for( int j=0; jsamples[i] * rate_mul, - px = (total / budget) * sw, - wx = (sample / budget) * sw; - - ui_rect block; - block[0^dir] = panel[0^dir] + px; - block[1^dir] = panel[1^dir] + (f32)i*sh; - block[2^dir] = VG_MAX( 1, wx-1 ); - block[3^dir] = ceilf(sh)-1; - ui_fill( block, colours[j] ); - - total += sample; - avgs[j] += sample; - } - } - - char infbuf[64]; - - snprintf( infbuf, 64, "accuracy: %.7fms", rate_mul ); - ui_text( (ui_rect){ panel[0] + 4, - panel[1] + panel[3] - 14, 500, 30 }, - infbuf, - 1, - k_ui_align_left, 0 ); - - for( int i=0; iname ); - - ui_text( (ui_rect){ panel[0] + 4, - panel[1] + panel[3] + 4 + i*14, - panel[2]-8, 14 }, - infbuf, 1, k_ui_align_left, 0 ); - } -} - -static void vg_profiler_init(void) -{ - VG_VAR_I32( vg_profiler, flags=VG_VAR_PERSISTENT ); -} - -#endif /* VG_PROFILER_H */ +void vg_profile_begin( struct vg_profile *profile ); +void vg_profile_increment( struct vg_profile *profile ); +void vg_profile_end( struct vg_profile *profile ); +void vg_profile_drawn( struct vg_profile **profiles, u32 count, + f64 budget, ui_rect panel, + int dir, i32 normalize ); +void vg_profiler_init(void); diff --git a/vg_rigidbody.c b/vg_rigidbody.c new file mode 100644 index 0000000..bcfebc5 --- /dev/null +++ b/vg_rigidbody.c @@ -0,0 +1,210 @@ +#include "vg_console.h" +#include "vg_m.h" +#include "vg_rigidbody.h" +#include "vg_platform.h" +#include "vg_engine.h" +#include + +static float + k_limit_bias = 0.02f, + k_joint_correction = 0.01f, + k_joint_impulse = 1.0f, + k_joint_bias = 0.08f; /* positional joints */ + +static void rb_register_cvar(void) +{ + VG_VAR_F32( k_limit_bias, flags=VG_VAR_CHEAT ); + VG_VAR_F32( k_joint_bias, flags=VG_VAR_CHEAT ); + VG_VAR_F32( k_joint_correction, flags=VG_VAR_CHEAT ); + VG_VAR_F32( k_joint_impulse, flags=VG_VAR_CHEAT ); +} + +void rb_setbody_capsule( rigidbody *rb, f32 r, f32 h, + f32 density, f32 inertia_scale ){ + f32 vol = vg_capsule_volume( r, h ), + mass = vol*density; + + rb->inv_mass = 1.0f/mass; + + m3x3f I; + vg_capsule_inertia( r, h, mass * inertia_scale, I ); + m3x3_inv( I, rb->iI ); +} + +void rb_setbody_box( rigidbody *rb, boxf box, f32 density, f32 inertia_scale ) +{ + f32 vol = vg_box_volume( box ), + mass = vol*density; + + rb->inv_mass = 1.0f/mass; + + m3x3f I; + vg_box_inertia( box, mass * inertia_scale, I ); + m3x3_inv( I, rb->iI ); +} + +void rb_setbody_sphere( rigidbody *rb, f32 r, f32 density, f32 inertia_scale ) +{ + f32 vol = vg_sphere_volume( r ), + mass = vol*density; + + rb->inv_mass = 1.0f/mass; + m3x3f I; + vg_sphere_inertia( r, mass * inertia_scale, I ); + m3x3_inv( I, rb->iI ); +} + +void rb_update_matrices( rigidbody *rb ) +{ + //q_normalize( rb->q ); + q_m3x3( rb->q, rb->to_world ); + v3_copy( rb->co, rb->to_world[3] ); + m4x3_invert_affine( rb->to_world, rb->to_local ); + + /* I = R I_0 R^T */ + m3x3_mul( rb->to_world, rb->iI, rb->iIw ); + m3x3_mul( rb->iIw, rb->to_local, rb->iIw ); +} + +void rb_extrapolate( rigidbody *rb, v3f co, v4f q ) +{ + float substep = vg.time_fixed_extrapolate; + v3_muladds( rb->co, rb->v, vg.time_fixed_delta*substep, co ); + + if( v3_length2( rb->w ) > 0.0f ){ + v4f rotation; + v3f axis; + v3_copy( rb->w, axis ); + + float mag = v3_length( axis ); + v3_divs( axis, mag, axis ); + q_axis_angle( rotation, axis, mag*vg.time_fixed_delta*substep ); + q_mul( rotation, rb->q, q ); + q_normalize( q ); + } + else{ + v4_copy( rb->q, q ); + } +} + +void rb_iter( rigidbody *rb ) +{ + if( !vg_validf( rb->v[0] ) || + !vg_validf( rb->v[1] ) || + !vg_validf( rb->v[2] ) ) + { + vg_fatal_error( "NaN velocity" ); + } + + v3f gravity = { 0.0f, -9.8f, 0.0f }; + v3_muladds( rb->v, gravity, vg.time_fixed_delta, rb->v ); + + /* intergrate velocity */ + v3_muladds( rb->co, rb->v, vg.time_fixed_delta, rb->co ); +#if 0 + v3_lerp( rb->w, (v3f){0.0f,0.0f,0.0f}, 0.0025f, rb->w ); +#endif + + /* inegrate inertia */ + if( v3_length2( rb->w ) > 0.0f ){ + v4f rotation; + v3f axis; + v3_copy( rb->w, axis ); + + float mag = v3_length( axis ); + v3_divs( axis, mag, axis ); + q_axis_angle( rotation, axis, mag*vg.time_fixed_delta ); + q_mul( rotation, rb->q, rb->q ); + q_normalize( rb->q ); + } +} + +/* + * based on: https://box2d.org/files/ErinCatto_NumericalMethods_GDC2015.pdf, + * page 76. + */ +void rb_solve_gyroscopic( rigidbody *rb, m3x3f I, f32 h ) +{ + /* convert to body coordinates */ + v3f w_local; + m3x3_mulv( rb->to_local, rb->w, w_local ); + + /* Residual vector */ + v3f f, v0; + m3x3_mulv( I, w_local, v0 ); + v3_cross( w_local, v0, f ); + v3_muls( f, h, f ); + + /* Jacobian */ + m3x3f iJ, J, skew_w_local, skew_v0, m0; + m3x3_skew_symetric( skew_w_local, w_local ); + m3x3_mul( skew_w_local, I, m0 ); + + m3x3_skew_symetric( skew_v0, v0 ); + m3x3_sub( m0, skew_v0, J ); + m3x3_scalef( J, h ); + m3x3_add( I, J, J ); + + /* Single Newton-Raphson update */ + v3f w1; + m3x3_inv( J, iJ ); + m3x3_mulv( iJ, f, w1 ); + v3_sub( w_local, w1, rb->w ); + + m3x3_mulv( rb->to_world, rb->w, rb->w ); +} + +void rb_rcv( rigidbody *rba, rigidbody *rbb, v3f ra, v3f rb, v3f rv ) +{ + v3f rva, rvb; + v3_cross( rba->w, ra, rva ); + v3_add( rba->v, rva, rva ); + v3_cross( rbb->w, rb, rvb ); + v3_add( rbb->v, rvb, rvb ); + + v3_sub( rva, rvb, rv ); +} + +void rb_linear_impulse( rigidbody *rb, v3f delta, v3f impulse ) +{ + /* linear */ + v3_muladds( rb->v, impulse, rb->inv_mass, rb->v ); + + /* Angular velocity */ + v3f wa; + v3_cross( delta, impulse, wa ); + + m3x3_mulv( rb->iIw, wa, wa ); + v3_add( rb->w, wa, rb->w ); +} + + +void rb_effect_simple_bouyency( rigidbody *ra, v4f plane, f32 amt, f32 drag ) +{ + /* float */ + float depth = v3_dot( plane, ra->co ) - plane[3], + lambda = vg_clampf( -depth, 0.0f, 1.0f ) * amt; + + v3_muladds( ra->v, plane, lambda * vg.time_fixed_delta, ra->v ); + + if( depth < 0.0f ) + v3_muls( ra->v, 1.0f-(drag*vg.time_fixed_delta), ra->v ); +} + +/* apply a spring&dampener force to match ra(worldspace) on rigidbody, to + * rt(worldspace) + */ +void rb_effect_spring_target_vector( rigidbody *rba, v3f ra, v3f rt, + f32 spring, f32 dampening, f32 timestep ) +{ + float d = v3_dot( rt, ra ); + float a = acosf( vg_clampf( d, -1.0f, 1.0f ) ); + + v3f axis; + v3_cross( rt, ra, axis ); + + float Fs = -a * spring, + Fd = -v3_dot( rba->w, axis ) * dampening; + + v3_muladds( rba->w, axis, (Fs+Fd) * timestep, rba->w ); +} diff --git a/vg_rigidbody.h b/vg_rigidbody.h index f6b9417..70be483 100644 --- a/vg_rigidbody.h +++ b/vg_rigidbody.h @@ -1,5 +1,4 @@ #pragma once - /* * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved * @@ -8,39 +7,14 @@ * vg_rigidbody_constraints.h */ -#include "vg_console.h" -#include - -/* - * ----------------------------------------------------------------------------- - * (K)onstants - * ----------------------------------------------------------------------------- - */ - -static const float - //k_rb_rate = (1.0/VG_TIMESTEP_FIXED), - //k_rb_delta = (1.0/k_rb_rate), - k_friction = 0.4f, - k_damp_linear = 0.1f, /* scale velocity 1/(1+x) */ - k_damp_angular = 0.1f, /* scale angular 1/(1+x) */ - k_penetration_slop = 0.01f, - k_inertia_scale = 4.0f, - k_phys_baumgarte = 0.2f, - k_gravity = 9.6f, - k_rb_density = 8.0f; - -static float - k_limit_bias = 0.02f, - k_joint_correction = 0.01f, - k_joint_impulse = 1.0f, - k_joint_bias = 0.08f; /* positional joints */ - -static void rb_register_cvar(void){ - VG_VAR_F32( k_limit_bias, flags=VG_VAR_CHEAT ); - VG_VAR_F32( k_joint_bias, flags=VG_VAR_CHEAT ); - VG_VAR_F32( k_joint_correction, flags=VG_VAR_CHEAT ); - VG_VAR_F32( k_joint_impulse, flags=VG_VAR_CHEAT ); -} +#define k_friction 0.4f +#define k_damp_linear 0.1f /* scale velocity 1/(1+x) */ +#define k_damp_angular 0.1f /* scale angular 1/(1+x) */ +#define k_penetration_slop 0.01f +#define k_rb_inertia_scale 4.0f +#define k_phys_baumgarte 0.2f +#define k_gravity 9.6f +#define k_rb_density 8.0f enum rb_shape { k_rb_shape_none = 0, @@ -49,20 +23,16 @@ enum rb_shape { k_rb_shape_capsule = 3, }; -/* - * ----------------------------------------------------------------------------- - * structure definitions - * ----------------------------------------------------------------------------- - */ - typedef struct rigidbody rigidbody; typedef struct rb_capsule rb_capsule; -struct rb_capsule{ +struct rb_capsule +{ f32 h, r; }; -struct rigidbody{ +struct rigidbody +{ v3f co, v, w; v4f q; @@ -75,202 +45,37 @@ struct rigidbody{ /* * Initialize rigidbody inverse mass and inertia tensor with some common shapes */ -static void rb_setbody_capsule( rigidbody *rb, f32 r, f32 h, - f32 density, f32 inertia_scale ){ - f32 vol = vg_capsule_volume( r, h ), - mass = vol*density; - - rb->inv_mass = 1.0f/mass; - - m3x3f I; - vg_capsule_inertia( r, h, mass * inertia_scale, I ); - m3x3_inv( I, rb->iI ); -} - -static void rb_setbody_box( rigidbody *rb, boxf box, - f32 density, f32 inertia_scale ){ - f32 vol = vg_box_volume( box ), - mass = vol*density; - - rb->inv_mass = 1.0f/mass; - - m3x3f I; - vg_box_inertia( box, mass * inertia_scale, I ); - m3x3_inv( I, rb->iI ); -} - -static void rb_setbody_sphere( rigidbody *rb, f32 r, - f32 density, f32 inertia_scale ){ - f32 vol = vg_sphere_volume( r ), - mass = vol*density; - - rb->inv_mass = 1.0f/mass; - m3x3f I; - vg_sphere_inertia( r, mass * inertia_scale, I ); - m3x3_inv( I, rb->iI ); -} +void rb_setbody_capsule( rigidbody *rb, f32 r, f32 h, + f32 density, f32 inertia_scale ); +void rb_setbody_box( rigidbody *rb, boxf box, f32 density, f32 inertia_scale ); +void rb_setbody_sphere( rigidbody *rb, f32 r, f32 density, f32 inertia_scale ); /* * Update ALL matrices and tensors on rigidbody */ -static void rb_update_matrices( rigidbody *rb ){ - //q_normalize( rb->q ); - q_m3x3( rb->q, rb->to_world ); - v3_copy( rb->co, rb->to_world[3] ); - m4x3_invert_affine( rb->to_world, rb->to_local ); - - /* I = R I_0 R^T */ - m3x3_mul( rb->to_world, rb->iI, rb->iIw ); - m3x3_mul( rb->iIw, rb->to_local, rb->iIw ); -} +void rb_update_matrices( rigidbody *rb ); /* * Extrapolate rigidbody into a transform based on vg accumulator. * Useful for rendering */ -static void rb_extrapolate( rigidbody *rb, v3f co, v4f q ){ - float substep = vg.time_fixed_extrapolate; - v3_muladds( rb->co, rb->v, vg.time_fixed_delta*substep, co ); - - if( v3_length2( rb->w ) > 0.0f ){ - v4f rotation; - v3f axis; - v3_copy( rb->w, axis ); - - float mag = v3_length( axis ); - v3_divs( axis, mag, axis ); - q_axis_angle( rotation, axis, mag*vg.time_fixed_delta*substep ); - q_mul( rotation, rb->q, q ); - q_normalize( q ); - } - else{ - v4_copy( rb->q, q ); - } -} - -static void rb_iter( rigidbody *rb ){ - if( !vg_validf( rb->v[0] ) || - !vg_validf( rb->v[1] ) || - !vg_validf( rb->v[2] ) ) - { - vg_fatal_error( "NaN velocity" ); - } - - v3f gravity = { 0.0f, -9.8f, 0.0f }; - v3_muladds( rb->v, gravity, vg.time_fixed_delta, rb->v ); - - /* intergrate velocity */ - v3_muladds( rb->co, rb->v, vg.time_fixed_delta, rb->co ); -#if 0 - v3_lerp( rb->w, (v3f){0.0f,0.0f,0.0f}, 0.0025f, rb->w ); -#endif - - /* inegrate inertia */ - if( v3_length2( rb->w ) > 0.0f ){ - v4f rotation; - v3f axis; - v3_copy( rb->w, axis ); - - float mag = v3_length( axis ); - v3_divs( axis, mag, axis ); - q_axis_angle( rotation, axis, mag*vg.time_fixed_delta ); - q_mul( rotation, rb->q, rb->q ); - q_normalize( rb->q ); - } -} - -/* - * based on: https://box2d.org/files/ErinCatto_NumericalMethods_GDC2015.pdf, - * page 76. - */ -static void rb_solve_gyroscopic( rigidbody *rb, m3x3f I, f32 h ){ - /* convert to body coordinates */ - v3f w_local; - m3x3_mulv( rb->to_local, rb->w, w_local ); - - /* Residual vector */ - v3f f, v0; - m3x3_mulv( I, w_local, v0 ); - v3_cross( w_local, v0, f ); - v3_muls( f, h, f ); - - /* Jacobian */ - m3x3f iJ, J, skew_w_local, skew_v0, m0; - m3x3_skew_symetric( skew_w_local, w_local ); - m3x3_mul( skew_w_local, I, m0 ); - - m3x3_skew_symetric( skew_v0, v0 ); - m3x3_sub( m0, skew_v0, J ); - m3x3_scalef( J, h ); - m3x3_add( I, J, J ); - - /* Single Newton-Raphson update */ - v3f w1; - m3x3_inv( J, iJ ); - m3x3_mulv( iJ, f, w1 ); - v3_sub( w_local, w1, rb->w ); - - m3x3_mulv( rb->to_world, rb->w, rb->w ); -} +void rb_extrapolate( rigidbody *rb, v3f co, v4f q ); +void rb_iter( rigidbody *rb ); +void rb_solve_gyroscopic( rigidbody *rb, m3x3f I, f32 h ); /* * Creates relative contact velocity vector */ -static void rb_rcv( rigidbody *rba, rigidbody *rbb, v3f ra, v3f rb, v3f rv ){ - v3f rva, rvb; - v3_cross( rba->w, ra, rva ); - v3_add( rba->v, rva, rva ); - v3_cross( rbb->w, rb, rvb ); - v3_add( rbb->v, rvb, rvb ); - - v3_sub( rva, rvb, rv ); -} +void rb_rcv( rigidbody *rba, rigidbody *rbb, v3f ra, v3f rb, v3f rv ); /* * Apply impulse to object */ -static void rb_linear_impulse( rigidbody *rb, v3f delta, v3f impulse ){ - /* linear */ - v3_muladds( rb->v, impulse, rb->inv_mass, rb->v ); - - /* Angular velocity */ - v3f wa; - v3_cross( delta, impulse, wa ); - - m3x3_mulv( rb->iIw, wa, wa ); - v3_add( rb->w, wa, rb->w ); -} +void rb_linear_impulse( rigidbody *rb, v3f delta, v3f impulse ); /* * Effectors */ - -static void rb_effect_simple_bouyency( rigidbody *ra, v4f plane, - float amt, float drag ){ - /* float */ - float depth = v3_dot( plane, ra->co ) - plane[3], - lambda = vg_clampf( -depth, 0.0f, 1.0f ) * amt; - - v3_muladds( ra->v, plane, lambda * vg.time_fixed_delta, ra->v ); - - if( depth < 0.0f ) - v3_muls( ra->v, 1.0f-(drag*vg.time_fixed_delta), ra->v ); -} - -/* apply a spring&dampener force to match ra(worldspace) on rigidbody, to - * rt(worldspace) - */ -static void rb_effect_spring_target_vector( rigidbody *rba, v3f ra, v3f rt, - float spring, float dampening, - float timestep ){ - float d = v3_dot( rt, ra ); - float a = acosf( vg_clampf( d, -1.0f, 1.0f ) ); - - v3f axis; - v3_cross( rt, ra, axis ); - - float Fs = -a * spring, - Fd = -v3_dot( rba->w, axis ) * dampening; - - v3_muladds( rba->w, axis, (Fs+Fd) * timestep, rba->w ); -} +void rb_effect_simple_bouyency( rigidbody *ra, v4f plane, f32 amt, f32 drag ); +void rb_effect_spring_target_vector( rigidbody *rba, v3f ra, v3f rt, + f32 spring, f32 dampening, f32 timestep ); diff --git a/vg_rigidbody_collision.c b/vg_rigidbody_collision.c new file mode 100644 index 0000000..f70a39b --- /dev/null +++ b/vg_rigidbody_collision.c @@ -0,0 +1,887 @@ +#include "vg_rigidbody.h" +#include "vg_rigidbody_collision.h" +#include "vg_m.h" +#include "vg_lines.h" +#include "vg_platform.h" + +int rb_contact_count = 0; +struct rb_ct rb_contact_buffer[VG_MAX_CONTACTS]; + +/* + * Contact generators + * + * These do not automatically allocate contacts, an appropriately sized + * buffer must be supplied. The function returns the size of the manifold + * which was generated. + * + * The values set on the contacts are: n, co, p, rba, rbb + */ + +/* + * By collecting the minimum(time) and maximum(time) pairs of points, we + * build a reduced and stable exact manifold. + * + * tx: time at point + * rx: minimum distance of these points + * dx: the delta between the two points + * + * pairs will only ammend these if they are creating a collision + */ +typedef struct capsule_manifold capsule_manifold; +struct capsule_manifold{ + f32 t0, t1; + f32 r0, r1; + v3f d0, d1; +}; + +/* + * Expand a line manifold with a new pair. t value is the time along segment + * on the oriented object which created this pair. + */ +static void rb_capsule_manifold( v3f pa, v3f pb, f32 t, f32 r, + capsule_manifold *manifold ){ + v3f delta; + v3_sub( pa, pb, delta ); + + if( v3_length2(delta) < r*r ){ + if( t < manifold->t0 ){ + v3_copy( delta, manifold->d0 ); + manifold->t0 = t; + manifold->r0 = r; + } + + if( t > manifold->t1 ){ + v3_copy( delta, manifold->d1 ); + manifold->t1 = t; + manifold->r1 = r; + } + } +} + +static void rb_capsule_manifold_init( capsule_manifold *manifold ){ + manifold->t0 = INFINITY; + manifold->t1 = -INFINITY; +} + +static int rb_capsule__manifold_done( m4x3f mtx, rb_capsule *c, + capsule_manifold *manifold, + rb_ct *buf ){ + v3f p0, p1; + v3_muladds( mtx[3], mtx[1], -c->h*0.5f+c->r, p0 ); + v3_muladds( mtx[3], mtx[1], c->h*0.5f-c->r, p1 ); + + int count = 0; + if( manifold->t0 <= 1.0f ){ + rb_ct *ct = buf; + + v3f pa; + v3_muls( p0, 1.0f-manifold->t0, pa ); + v3_muladds( pa, p1, manifold->t0, pa ); + + f32 d = v3_length( manifold->d0 ); + v3_muls( manifold->d0, 1.0f/d, ct->n ); + v3_muladds( pa, ct->n, -c->r, ct->co ); + + ct->p = manifold->r0 - d; + ct->type = k_contact_type_default; + count ++; + } + + if( (manifold->t1 >= 0.0f) && (manifold->t0 != manifold->t1) ){ + rb_ct *ct = buf+count; + + v3f pa; + v3_muls( p0, 1.0f-manifold->t1, pa ); + v3_muladds( pa, p1, manifold->t1, pa ); + + f32 d = v3_length( manifold->d1 ); + v3_muls( manifold->d1, 1.0f/d, ct->n ); + v3_muladds( pa, ct->n, -c->r, ct->co ); + + ct->p = manifold->r1 - d; + ct->type = k_contact_type_default; + + count ++; + } + + /* + * Debugging + */ + +#if 0 + if( count == 2 ) + vg_line( buf[0].co, buf[1].co, 0xff0000ff ); +#endif + + return count; +} + +int rb_capsule__sphere( m4x3f mtxA, rb_capsule *ca, + v3f coB, f32 rb, rb_ct *buf ){ + f32 ha = ca->h, + ra = ca->r, + r = ra + rb; + + v3f p0, p1; + v3_muladds( mtxA[3], mtxA[1], -ha*0.5f+ra, p0 ); + v3_muladds( mtxA[3], mtxA[1], ha*0.5f-ra, p1 ); + + v3f c, delta; + closest_point_segment( p0, p1, coB, c ); + v3_sub( c, coB, delta ); + f32 d2 = v3_length2(delta); + + if( d2 < r*r ){ + f32 d = sqrtf(d2); + + rb_ct *ct = buf; + v3_muls( delta, 1.0f/d, ct->n ); + ct->p = r-d; + + v3f p0, p1; + v3_muladds( c, ct->n, -ra, p0 ); + v3_muladds( coB, ct->n, rb, p1 ); + v3_add( p0, p1, ct->co ); + v3_muls( ct->co, 0.5f, ct->co ); + ct->type = k_contact_type_default; + return 1; + } + else return 0; +} + +int rb_capsule__capsule( m4x3f mtxA, rb_capsule *ca, + m4x3f mtxB, rb_capsule *cb, rb_ct *buf ) +{ + f32 ha = ca->h, + hb = cb->h, + ra = ca->r, + rb = cb->r, + r = ra+rb; + + v3f p0, p1, p2, p3; + v3_muladds( mtxA[3], mtxA[1], -ha*0.5f+ra, p0 ); + v3_muladds( mtxA[3], mtxA[1], ha*0.5f-ra, p1 ); + v3_muladds( mtxB[3], mtxB[1], -hb*0.5f+rb, p2 ); + v3_muladds( mtxB[3], mtxB[1], hb*0.5f-rb, p3 ); + + capsule_manifold manifold; + rb_capsule_manifold_init( &manifold ); + + v3f pa, pb; + f32 ta, tb; + closest_segment_segment( p0, p1, p2, p3, &ta, &tb, pa, pb ); + rb_capsule_manifold( pa, pb, ta, r, &manifold ); + + ta = closest_point_segment( p0, p1, p2, pa ); + tb = closest_point_segment( p0, p1, p3, pb ); + rb_capsule_manifold( pa, p2, ta, r, &manifold ); + rb_capsule_manifold( pb, p3, tb, r, &manifold ); + + closest_point_segment( p2, p3, p0, pa ); + closest_point_segment( p2, p3, p1, pb ); + rb_capsule_manifold( p0, pa, 0.0f, r, &manifold ); + rb_capsule_manifold( p1, pb, 1.0f, r, &manifold ); + + return rb_capsule__manifold_done( mtxA, ca, &manifold, buf ); +} + +/* + * Generates up to two contacts; optimised for the most stable manifold + */ +int rb_capsule__box( m4x3f mtxA, rb_capsule *ca, + m4x3f mtxB, m4x3f mtxB_inverse, boxf box, + rb_ct *buf ) +{ + f32 h = ca->h, r = ca->r; + + /* + * Solving this in symetric local space of the cube saves us some time and a + * couple branches when it comes to the quad stage. + */ + v3f centroid; + v3_add( box[0], box[1], centroid ); + v3_muls( centroid, 0.5f, centroid ); + + boxf bbx; + v3_sub( box[0], centroid, bbx[0] ); + v3_sub( box[1], centroid, bbx[1] ); + + v3f pc, p0w, p1w, p0, p1; + v3_muladds( mtxA[3], mtxA[1], -h*0.5f+r, p0w ); + v3_muladds( mtxA[3], mtxA[1], h*0.5f-r, p1w ); + + m4x3_mulv( mtxB_inverse, p0w, p0 ); + m4x3_mulv( mtxB_inverse, p1w, p1 ); + v3_sub( p0, centroid, p0 ); + v3_sub( p1, centroid, p1 ); + v3_add( p0, p1, pc ); + v3_muls( pc, 0.5f, pc ); + + /* + * Finding an appropriate quad to collide lines with + */ + v3f region; + v3_div( pc, bbx[1], region ); + + v3f quad[4]; + if( (fabsf(region[0]) > fabsf(region[1])) && + (fabsf(region[0]) > fabsf(region[2])) ) + { + f32 px = vg_signf(region[0]) * bbx[1][0]; + v3_copy( (v3f){ px, bbx[0][1], bbx[0][2] }, quad[0] ); + v3_copy( (v3f){ px, bbx[1][1], bbx[0][2] }, quad[1] ); + v3_copy( (v3f){ px, bbx[1][1], bbx[1][2] }, quad[2] ); + v3_copy( (v3f){ px, bbx[0][1], bbx[1][2] }, quad[3] ); + } + else if( fabsf(region[1]) > fabsf(region[2]) ) + { + f32 py = vg_signf(region[1]) * bbx[1][1]; + v3_copy( (v3f){ bbx[0][0], py, bbx[0][2] }, quad[0] ); + v3_copy( (v3f){ bbx[1][0], py, bbx[0][2] }, quad[1] ); + v3_copy( (v3f){ bbx[1][0], py, bbx[1][2] }, quad[2] ); + v3_copy( (v3f){ bbx[0][0], py, bbx[1][2] }, quad[3] ); + } + else + { + f32 pz = vg_signf(region[2]) * bbx[1][2]; + v3_copy( (v3f){ bbx[0][0], bbx[0][1], pz }, quad[0] ); + v3_copy( (v3f){ bbx[1][0], bbx[0][1], pz }, quad[1] ); + v3_copy( (v3f){ bbx[1][0], bbx[1][1], pz }, quad[2] ); + v3_copy( (v3f){ bbx[0][0], bbx[1][1], pz }, quad[3] ); + } + + capsule_manifold manifold; + rb_capsule_manifold_init( &manifold ); + + v3f c0, c1; + closest_point_aabb( p0, bbx, c0 ); + closest_point_aabb( p1, bbx, c1 ); + + v3f d0, d1, da; + v3_sub( c0, p0, d0 ); + v3_sub( c1, p1, d1 ); + v3_sub( p1, p0, da ); + + /* TODO: ? */ + v3_normalize(d0); + v3_normalize(d1); + v3_normalize(da); + + if( v3_dot( da, d0 ) <= 0.01f ) + rb_capsule_manifold( p0, c0, 0.0f, r, &manifold ); + + if( v3_dot( da, d1 ) >= -0.01f ) + rb_capsule_manifold( p1, c1, 1.0f, r, &manifold ); + + for( i32 i=0; i<4; i++ ){ + i32 i0 = i, + i1 = (i+1)%4; + + v3f ca, cb; + f32 ta, tb; + closest_segment_segment( p0, p1, quad[i0], quad[i1], &ta, &tb, ca, cb ); + rb_capsule_manifold( ca, cb, ta, r, &manifold ); + } + + /* + * Create final contacts based on line manifold + */ + m3x3_mulv( mtxB, manifold.d0, manifold.d0 ); + m3x3_mulv( mtxB, manifold.d1, manifold.d1 ); + return rb_capsule__manifold_done( mtxA, ca, &manifold, buf ); +} + +int rb_sphere__box( v3f coA, f32 ra, + m4x3f mtxB, m4x3f mtxB_inverse, boxf box, + rb_ct *buf ) +{ + v3f co, delta; + closest_point_obb( coA, box, mtxB, mtxB_inverse, co ); + v3_sub( coA, co, delta ); + + f32 d2 = v3_length2(delta); + + if( d2 <= ra*ra ){ + f32 d; + + rb_ct *ct = buf; + if( d2 <= 0.0001f ){ + v3f e, coB; + v3_sub( box[1], box[0], e ); + v3_muls( e, 0.5f, e ); + v3_add( box[0], e, coB ); + v3_sub( coA, coB, delta ); + + /* + * some extra testing is required to find the best axis to push the + * object back outside the box. Since there isnt a clear seperating + * vector already, especially on really high aspect boxes. + */ + f32 lx = v3_dot( mtxB[0], delta ), + ly = v3_dot( mtxB[1], delta ), + lz = v3_dot( mtxB[2], delta ), + px = e[0] - fabsf(lx), + py = e[1] - fabsf(ly), + pz = e[2] - fabsf(lz); + + if( px < py && px < pz ) v3_muls( mtxB[0], vg_signf(lx), ct->n ); + else if( py < pz ) v3_muls( mtxB[1], vg_signf(ly), ct->n ); + else v3_muls( mtxB[2], vg_signf(lz), ct->n ); + + v3_muladds( coA, ct->n, -ra, ct->co ); + ct->p = ra; + } + else{ + d = sqrtf(d2); + v3_muls( delta, 1.0f/d, ct->n ); + ct->p = ra-d; + v3_copy( co, ct->co ); + } + + ct->type = k_contact_type_default; + return 1; + } + else return 0; +} + +int rb_sphere__sphere( v3f coA, f32 ra, v3f coB, f32 rb, rb_ct *buf ) +{ + v3f delta; + v3_sub( coA, coB, delta ); + + f32 d2 = v3_length2(delta), + r = ra+rb; + + if( d2 < r*r ){ + f32 d = sqrtf(d2); + + rb_ct *ct = buf; + v3_muls( delta, 1.0f/d, ct->n ); + + v3f p0, p1; + v3_muladds( coA, ct->n,-ra, p0 ); + v3_muladds( coB, ct->n, rb, p1 ); + v3_add( p0, p1, ct->co ); + v3_muls( ct->co, 0.5f, ct->co ); + ct->type = k_contact_type_default; + ct->p = r-d; + return 1; + } + else return 0; +} + +int rb_sphere__triangle( m4x3f mtxA, f32 r, v3f tri[3], rb_ct *buf ) +{ + v3f delta, co; + enum contact_type type = closest_on_triangle_1( mtxA[3], tri, co ); + v3_sub( mtxA[3], co, delta ); + f32 d2 = v3_length2( delta ); + + if( d2 <= r*r ){ + rb_ct *ct = buf; + + v3f ab, ac, tn; + v3_sub( tri[2], tri[0], ab ); + v3_sub( tri[1], tri[0], ac ); + v3_cross( ac, ab, tn ); + v3_copy( tn, ct->n ); + + if( v3_length2( ct->n ) <= 0.00001f ){ +#ifdef RIGIDBODY_CRY_ABOUT_EVERYTHING + vg_error( "Zero area triangle!\n" ); +#endif + return 0; + } + + v3_normalize( ct->n ); + + f32 d = sqrtf(d2); + + v3_copy( co, ct->co ); + ct->type = type; + ct->p = r-d; + return 1; + } + + return 0; +} + +int rb_capsule__triangle( m4x3f mtxA, rb_capsule *c, v3f tri[3], rb_ct *buf ) +{ + v3f pc, p0w, p1w; + v3_muladds( mtxA[3], mtxA[1], -c->h*0.5f+c->r, p0w ); + v3_muladds( mtxA[3], mtxA[1], c->h*0.5f-c->r, p1w ); + + capsule_manifold manifold; + rb_capsule_manifold_init( &manifold ); + + v3f v0, v1, n; + v3_sub( tri[1], tri[0], v0 ); + v3_sub( tri[2], tri[0], v1 ); + v3_cross( v0, v1, n ); + + if( v3_length2( n ) <= 0.00001f ){ +#ifdef RIGIDBODY_CRY_ABOUT_EVERYTHING + vg_error( "Zero area triangle!\n" ); +#endif + return 0; + } + + v3_normalize( n ); + +#if 1 + /* deep penetration recovery. for when we clip through the triangles. so its + * not very 'correct' */ + f32 dist; + if( ray_tri( tri, p0w, mtxA[1], &dist, 1 ) ){ + f32 l = c->h - c->r*2.0f; + if( (dist >= 0.0f) && (dist < l) ){ + v3f co; + v3_muladds( p0w, mtxA[1], dist, co ); + vg_line_point( co, 0.02f, 0xffffff00 ); + + v3f d0, d1; + v3_sub( p0w, co, d0 ); + v3_sub( p1w, co, d1 ); + + f32 p = vg_minf( v3_dot( n, d0 ), v3_dot( n, d1 ) ) - c->r; + + rb_ct *ct = buf; + ct->p = -p; + ct->type = k_contact_type_default; + v3_copy( n, ct->n ); + v3_muladds( co, n, p, ct->co ); + + return 1; + } + } +#endif + + v3f c0, c1; + closest_on_triangle_1( p0w, tri, c0 ); + closest_on_triangle_1( p1w, tri, c1 ); + + v3f d0, d1, da; + v3_sub( c0, p0w, d0 ); + v3_sub( c1, p1w, d1 ); + v3_sub( p1w, p0w, da ); + + v3_normalize(d0); + v3_normalize(d1); + v3_normalize(da); + + /* the two balls at the ends */ + if( v3_dot( da, d0 ) <= 0.01f ) + rb_capsule_manifold( p0w, c0, 0.0f, c->r, &manifold ); + if( v3_dot( da, d1 ) >= -0.01f ) + rb_capsule_manifold( p1w, c1, 1.0f, c->r, &manifold ); + + /* the edges to edges */ + for( int i=0; i<3; i++ ){ + int i0 = i, + i1 = (i+1)%3; + + v3f ca, cb; + f32 ta, tb; + closest_segment_segment( p0w, p1w, tri[i0], tri[i1], &ta, &tb, ca, cb ); + rb_capsule_manifold( ca, cb, ta, c->r, &manifold ); + } + + int count = rb_capsule__manifold_done( mtxA, c, &manifold, buf ); + for( int i=0; i vg_list_size(rb_contact_buffer) ) + return 0; + + return 1; +} + +rb_ct *rb_global_buffer( void ) +{ + return &rb_contact_buffer[ rb_contact_count ]; +} + +/* + * ----------------------------------------------------------------------------- + * Boolean shape overlap functions + * ----------------------------------------------------------------------------- + */ + +/* + * Project AABB, and triangle interval onto axis to check if they overlap + */ +static int rb_box_triangle_interval( v3f extent, v3f axis, v3f tri[3] ){ + float + + r = extent[0] * fabsf(axis[0]) + + extent[1] * fabsf(axis[1]) + + extent[2] * fabsf(axis[2]), + + p0 = v3_dot( axis, tri[0] ), + p1 = v3_dot( axis, tri[1] ), + p2 = v3_dot( axis, tri[2] ), + + e = vg_maxf(-vg_maxf(p0,vg_maxf(p1,p2)), vg_minf(p0,vg_minf(p1,p2))); + + if( e > r ) return 0; + else return 1; +} + +/* + * Seperating axis test box vs triangle + */ +int rb_box_triangle_sat( v3f extent, v3f center, + m4x3f to_local, v3f tri_src[3] ) +{ + v3f tri[3]; + + for( int i=0; i<3; i++ ){ + m4x3_mulv( to_local, tri_src[i], tri[i] ); + v3_sub( tri[i], center, tri[i] ); + } + + v3f f0,f1,f2,n; + v3_sub( tri[1], tri[0], f0 ); + v3_sub( tri[2], tri[1], f1 ); + v3_sub( tri[0], tri[2], f2 ); + + + v3f axis[9]; + v3_cross( (v3f){1.0f,0.0f,0.0f}, f0, axis[0] ); + v3_cross( (v3f){1.0f,0.0f,0.0f}, f1, axis[1] ); + v3_cross( (v3f){1.0f,0.0f,0.0f}, f2, axis[2] ); + v3_cross( (v3f){0.0f,1.0f,0.0f}, f0, axis[3] ); + v3_cross( (v3f){0.0f,1.0f,0.0f}, f1, axis[4] ); + v3_cross( (v3f){0.0f,1.0f,0.0f}, f2, axis[5] ); + v3_cross( (v3f){0.0f,0.0f,1.0f}, f0, axis[6] ); + v3_cross( (v3f){0.0f,0.0f,1.0f}, f1, axis[7] ); + v3_cross( (v3f){0.0f,0.0f,1.0f}, f2, axis[8] ); + + for( int i=0; i<9; i++ ) + if(!rb_box_triangle_interval( extent, axis[i], tri )) return 0; + + /* u0, u1, u2 */ + if(!rb_box_triangle_interval( extent, (v3f){1.0f,0.0f,0.0f}, tri )) return 0; + if(!rb_box_triangle_interval( extent, (v3f){0.0f,1.0f,0.0f}, tri )) return 0; + if(!rb_box_triangle_interval( extent, (v3f){0.0f,0.0f,1.0f}, tri )) return 0; + + /* normal */ + v3_cross( f0, f1, n ); + if(!rb_box_triangle_interval( extent, n, tri )) return 0; + + return 1; +} + +/* + * ----------------------------------------------------------------------------- + * Manifold + * ----------------------------------------------------------------------------- + */ + +int rb_manifold_apply_filtered( rb_ct *man, int len ) +{ + int k = 0; + + for( int i=0; itype == k_contact_type_disabled ) + continue; + + man[k ++] = man[i]; + } + + return k; +} + +void rb_manifold_contact_weld( rb_ct *ci, rb_ct *cj, float r ) +{ + if( v3_dist2( ci->co, cj->co ) < r*r ){ + cj->type = k_contact_type_disabled; + ci->p = (ci->p + cj->p) * 0.5f; + + v3_add( ci->co, cj->co, ci->co ); + v3_muls( ci->co, 0.5f, ci->co ); + + v3f delta; + v3_sub( ci->rba->co, ci->co, delta ); + + float c0 = v3_dot( ci->n, delta ), + c1 = v3_dot( cj->n, delta ); + + if( c0 < 0.0f || c1 < 0.0f ){ + /* error */ + ci->type = k_contact_type_disabled; + } + else{ + v3f n; + v3_muls( ci->n, c0, n ); + v3_muladds( n, cj->n, c1, n ); + v3_normalize( n ); + v3_copy( n, ci->n ); + } + } +} + +/* + * + */ +void rb_manifold_filter_joint_edges( rb_ct *man, int len, float r ) +{ + for( int i=0; itype != k_contact_type_edge ) + continue; + + for( int j=i+1; jtype != k_contact_type_edge ) + continue; + + rb_manifold_contact_weld( ci, cj, r ); + } + } +} + +void rb_manifold_filter_pairs( rb_ct *man, int len, float r ) +{ + for( int i=0; itype == k_contact_type_disabled ) continue; + + for( int j=i+1; jtype == k_contact_type_disabled ) continue; + + if( v3_dist2( ci->co, cj->co ) < r*r ){ + cj->type = k_contact_type_disabled; + v3_add( cj->n, ci->n, ci->n ); + ci->p += cj->p; + similar ++; + } + } + + if( similar ){ + float n = 1.0f/((float)similar+1.0f); + v3_muls( ci->n, n, ci->n ); + ci->p *= n; + + if( v3_length2(ci->n) < 0.1f*0.1f ) + ci->type = k_contact_type_disabled; + else + v3_normalize( ci->n ); + } + } +} + +void rb_manifold_filter_backface( rb_ct *man, int len ) +{ + for( int i=0; itype == k_contact_type_disabled ) + continue; + + v3f delta; + v3_sub( ct->co, ct->rba->co, delta ); + + if( v3_dot( delta, ct->n ) > -0.001f ) + ct->type = k_contact_type_disabled; + } +} + +void rb_manifold_filter_coplanar( rb_ct *man, int len, float w ) +{ + for( int i=0; itype == k_contact_type_disabled || + ci->type == k_contact_type_edge ) + continue; + + float d1 = v3_dot( ci->co, ci->n ); + + for( int j=0; jtype == k_contact_type_disabled ) + continue; + + float d2 = v3_dot( cj->co, ci->n ), + d = d2-d1; + + if( fabsf( d ) <= w ){ + cj->type = k_contact_type_disabled; + } + } + } +} + +void rb_debug_contact( rb_ct *ct ) +{ + v3f p1; + v3_muladds( ct->co, ct->n, 0.05f, p1 ); + + if( ct->type == k_contact_type_default ){ + vg_line_point( ct->co, 0.0125f, 0xff0000ff ); + vg_line( ct->co, p1, 0xffffffff ); + } + else if( ct->type == k_contact_type_edge ){ + vg_line_point( ct->co, 0.0125f, 0xff00ffc0 ); + vg_line( ct->co, p1, 0xffffffff ); + } +} + +void rb_solver_reset(void) +{ + rb_contact_count = 0; +} + +rb_ct *rb_global_ct(void) +{ + return rb_contact_buffer + rb_contact_count; +} + +void rb_prepare_contact( rb_ct *ct, f32 dt ) +{ + ct->bias = -k_phys_baumgarte * (dt*3600.0f) + * vg_minf( 0.0f, -ct->p+k_penetration_slop ); + + v3_tangent_basis( ct->n, ct->t[0], ct->t[1] ); + ct->norm_impulse = 0.0f; + ct->tangent_impulse[0] = 0.0f; + ct->tangent_impulse[1] = 0.0f; +} + +/* + * calculate total move to depenetrate object from contacts. + * manifold should belong to ONE object only + */ +void rb_depenetrate( rb_ct *manifold, int len, v3f dt ) +{ + v3_zero( dt ); + + for( int j=0; j<7; j++ ){ + for( int i=0; in, dt ), + remaining = (ct->p-k_penetration_slop) - resolved_amt, + apply = vg_maxf( remaining, 0.0f ) * 0.4f; + + v3_muladds( dt, ct->n, apply, dt ); + } + } +} + +/* + * Initializing things like tangent vectors + */ +void rb_presolve_contacts( rb_ct *buffer, f32 dt, int len ) +{ + for( int i=0; ico, ct->rba->co, ra ); + v3_sub( ct->co, ct->rbb->co, rb ); + v3_cross( ra, ct->n, raCn ); + v3_cross( rb, ct->n, rbCn ); + + /* orient inverse inertia tensors */ + v3f raCnI, rbCnI; + m3x3_mulv( ct->rba->iIw, raCn, raCnI ); + m3x3_mulv( ct->rbb->iIw, rbCn, rbCnI ); + + ct->normal_mass = ct->rba->inv_mass + ct->rbb->inv_mass; + ct->normal_mass += v3_dot( raCn, raCnI ); + ct->normal_mass += v3_dot( rbCn, rbCnI ); + ct->normal_mass = 1.0f/ct->normal_mass; + + for( int j=0; j<2; j++ ){ + v3f raCtI, rbCtI; + v3_cross( ct->t[j], ra, raCt ); + v3_cross( ct->t[j], rb, rbCt ); + m3x3_mulv( ct->rba->iIw, raCt, raCtI ); + m3x3_mulv( ct->rbb->iIw, rbCt, rbCtI ); + + ct->tangent_mass[j] = ct->rba->inv_mass + ct->rbb->inv_mass; + ct->tangent_mass[j] += v3_dot( raCt, raCtI ); + ct->tangent_mass[j] += v3_dot( rbCt, rbCtI ); + ct->tangent_mass[j] = 1.0f/ct->tangent_mass[j]; + } + } +} + +void rb_contact_restitution( rb_ct *ct, float cr ) +{ + v3f rv, ra, rb; + v3_sub( ct->co, ct->rba->co, ra ); + v3_sub( ct->co, ct->rbb->co, rb ); + rb_rcv( ct->rba, ct->rbb, ra, rb, rv ); + + float v = v3_dot( rv, ct->n ); + + if( v < -1.0f ){ + ct->bias += -cr * v; + } +} + +/* + * One iteration to solve the contact constraint + */ +void rb_solve_contacts( rb_ct *buf, int len ) +{ + for( int i=0; ico, ct->rba->co, ra ); + v3_sub( ct->co, ct->rbb->co, rb ); + rb_rcv( ct->rba, ct->rbb, ra, rb, rv ); + + /* Friction */ + for( int j=0; j<2; j++ ){ + float f = k_friction * ct->norm_impulse, + vt = v3_dot( rv, ct->t[j] ), + lambda = ct->tangent_mass[j] * -vt; + + float temp = ct->tangent_impulse[j]; + ct->tangent_impulse[j] = vg_clampf( temp + lambda, -f, f ); + lambda = ct->tangent_impulse[j] - temp; + + v3f impulse; + v3_muls( ct->t[j], lambda, impulse ); + rb_linear_impulse( ct->rba, ra, impulse ); + + v3_muls( ct->t[j], -lambda, impulse ); + rb_linear_impulse( ct->rbb, rb, impulse ); + } + + /* Normal */ + rb_rcv( ct->rba, ct->rbb, ra, rb, rv ); + float vn = v3_dot( rv, ct->n ), + lambda = ct->normal_mass * (-vn + ct->bias); + + float temp = ct->norm_impulse; + ct->norm_impulse = vg_maxf( temp + lambda, 0.0f ); + lambda = ct->norm_impulse - temp; + + v3f impulse; + v3_muls( ct->n, lambda, impulse ); + rb_linear_impulse( ct->rba, ra, impulse ); + + v3_muls( ct->n, -lambda, impulse ); + rb_linear_impulse( ct->rbb, rb, impulse ); + } +} diff --git a/vg_rigidbody_collision.h b/vg_rigidbody_collision.h index c08baa5..e0cf3e3 100644 --- a/vg_rigidbody_collision.h +++ b/vg_rigidbody_collision.h @@ -1,12 +1,11 @@ #pragma once -#include "vg_rigidbody.h" -#ifndef VG_MAX_CONTACTS +/* TODO: Get rid of this! */ #define VG_MAX_CONTACTS 256 -#endif typedef struct rb_ct rb_ct; -static struct rb_ct{ +struct rb_ct +{ rigidbody *rba, *rbb; v3f co, n; v3f t[2]; @@ -17,877 +16,55 @@ static struct rb_ct{ enum contact_type type; } -rb_contact_buffer[VG_MAX_CONTACTS]; -static int rb_contact_count = 0; - -/* - * Contact generators - * - * These do not automatically allocate contacts, an appropriately sized - * buffer must be supplied. The function returns the size of the manifold - * which was generated. - * - * The values set on the contacts are: n, co, p, rba, rbb - */ - -/* - * By collecting the minimum(time) and maximum(time) pairs of points, we - * build a reduced and stable exact manifold. - * - * tx: time at point - * rx: minimum distance of these points - * dx: the delta between the two points - * - * pairs will only ammend these if they are creating a collision - */ -typedef struct capsule_manifold capsule_manifold; -struct capsule_manifold{ - f32 t0, t1; - f32 r0, r1; - v3f d0, d1; -}; - -/* - * Expand a line manifold with a new pair. t value is the time along segment - * on the oriented object which created this pair. - */ -static void rb_capsule_manifold( v3f pa, v3f pb, f32 t, f32 r, - capsule_manifold *manifold ){ - v3f delta; - v3_sub( pa, pb, delta ); - - if( v3_length2(delta) < r*r ){ - if( t < manifold->t0 ){ - v3_copy( delta, manifold->d0 ); - manifold->t0 = t; - manifold->r0 = r; - } - - if( t > manifold->t1 ){ - v3_copy( delta, manifold->d1 ); - manifold->t1 = t; - manifold->r1 = r; - } - } -} - -static void rb_capsule_manifold_init( capsule_manifold *manifold ){ - manifold->t0 = INFINITY; - manifold->t1 = -INFINITY; -} - -static int rb_capsule__manifold_done( m4x3f mtx, rb_capsule *c, - capsule_manifold *manifold, - rb_ct *buf ){ - v3f p0, p1; - v3_muladds( mtx[3], mtx[1], -c->h*0.5f+c->r, p0 ); - v3_muladds( mtx[3], mtx[1], c->h*0.5f-c->r, p1 ); - - int count = 0; - if( manifold->t0 <= 1.0f ){ - rb_ct *ct = buf; - - v3f pa; - v3_muls( p0, 1.0f-manifold->t0, pa ); - v3_muladds( pa, p1, manifold->t0, pa ); - - f32 d = v3_length( manifold->d0 ); - v3_muls( manifold->d0, 1.0f/d, ct->n ); - v3_muladds( pa, ct->n, -c->r, ct->co ); - - ct->p = manifold->r0 - d; - ct->type = k_contact_type_default; - count ++; - } - - if( (manifold->t1 >= 0.0f) && (manifold->t0 != manifold->t1) ){ - rb_ct *ct = buf+count; - - v3f pa; - v3_muls( p0, 1.0f-manifold->t1, pa ); - v3_muladds( pa, p1, manifold->t1, pa ); - - f32 d = v3_length( manifold->d1 ); - v3_muls( manifold->d1, 1.0f/d, ct->n ); - v3_muladds( pa, ct->n, -c->r, ct->co ); - - ct->p = manifold->r1 - d; - ct->type = k_contact_type_default; - - count ++; - } - - /* - * Debugging - */ - -#if 0 - if( count == 2 ) - vg_line( buf[0].co, buf[1].co, 0xff0000ff ); -#endif - - return count; -} - -static int rb_capsule__sphere( m4x3f mtxA, rb_capsule *ca, - v3f coB, f32 rb, rb_ct *buf ){ - f32 ha = ca->h, - ra = ca->r, - r = ra + rb; - - v3f p0, p1; - v3_muladds( mtxA[3], mtxA[1], -ha*0.5f+ra, p0 ); - v3_muladds( mtxA[3], mtxA[1], ha*0.5f-ra, p1 ); - - v3f c, delta; - closest_point_segment( p0, p1, coB, c ); - v3_sub( c, coB, delta ); - f32 d2 = v3_length2(delta); - - if( d2 < r*r ){ - f32 d = sqrtf(d2); - - rb_ct *ct = buf; - v3_muls( delta, 1.0f/d, ct->n ); - ct->p = r-d; - - v3f p0, p1; - v3_muladds( c, ct->n, -ra, p0 ); - v3_muladds( coB, ct->n, rb, p1 ); - v3_add( p0, p1, ct->co ); - v3_muls( ct->co, 0.5f, ct->co ); - ct->type = k_contact_type_default; - return 1; - } - else return 0; -} - -static int rb_capsule__capsule( m4x3f mtxA, rb_capsule *ca, - m4x3f mtxB, rb_capsule *cb, rb_ct *buf ){ - f32 ha = ca->h, - hb = cb->h, - ra = ca->r, - rb = cb->r, - r = ra+rb; - - v3f p0, p1, p2, p3; - v3_muladds( mtxA[3], mtxA[1], -ha*0.5f+ra, p0 ); - v3_muladds( mtxA[3], mtxA[1], ha*0.5f-ra, p1 ); - v3_muladds( mtxB[3], mtxB[1], -hb*0.5f+rb, p2 ); - v3_muladds( mtxB[3], mtxB[1], hb*0.5f-rb, p3 ); - - capsule_manifold manifold; - rb_capsule_manifold_init( &manifold ); - - v3f pa, pb; - f32 ta, tb; - closest_segment_segment( p0, p1, p2, p3, &ta, &tb, pa, pb ); - rb_capsule_manifold( pa, pb, ta, r, &manifold ); - - ta = closest_point_segment( p0, p1, p2, pa ); - tb = closest_point_segment( p0, p1, p3, pb ); - rb_capsule_manifold( pa, p2, ta, r, &manifold ); - rb_capsule_manifold( pb, p3, tb, r, &manifold ); - - closest_point_segment( p2, p3, p0, pa ); - closest_point_segment( p2, p3, p1, pb ); - rb_capsule_manifold( p0, pa, 0.0f, r, &manifold ); - rb_capsule_manifold( p1, pb, 1.0f, r, &manifold ); - - return rb_capsule__manifold_done( mtxA, ca, &manifold, buf ); -} - -/* - * Generates up to two contacts; optimised for the most stable manifold - */ -static int rb_capsule__box( m4x3f mtxA, rb_capsule *ca, - m4x3f mtxB, m4x3f mtxB_inverse, boxf box, - rb_ct *buf ){ - f32 h = ca->h, r = ca->r; - - /* - * Solving this in symetric local space of the cube saves us some time and a - * couple branches when it comes to the quad stage. - */ - v3f centroid; - v3_add( box[0], box[1], centroid ); - v3_muls( centroid, 0.5f, centroid ); - - boxf bbx; - v3_sub( box[0], centroid, bbx[0] ); - v3_sub( box[1], centroid, bbx[1] ); - - v3f pc, p0w, p1w, p0, p1; - v3_muladds( mtxA[3], mtxA[1], -h*0.5f+r, p0w ); - v3_muladds( mtxA[3], mtxA[1], h*0.5f-r, p1w ); - - m4x3_mulv( mtxB_inverse, p0w, p0 ); - m4x3_mulv( mtxB_inverse, p1w, p1 ); - v3_sub( p0, centroid, p0 ); - v3_sub( p1, centroid, p1 ); - v3_add( p0, p1, pc ); - v3_muls( pc, 0.5f, pc ); - - /* - * Finding an appropriate quad to collide lines with - */ - v3f region; - v3_div( pc, bbx[1], region ); - - v3f quad[4]; - if( (fabsf(region[0]) > fabsf(region[1])) && - (fabsf(region[0]) > fabsf(region[2])) ) - { - f32 px = vg_signf(region[0]) * bbx[1][0]; - v3_copy( (v3f){ px, bbx[0][1], bbx[0][2] }, quad[0] ); - v3_copy( (v3f){ px, bbx[1][1], bbx[0][2] }, quad[1] ); - v3_copy( (v3f){ px, bbx[1][1], bbx[1][2] }, quad[2] ); - v3_copy( (v3f){ px, bbx[0][1], bbx[1][2] }, quad[3] ); - } - else if( fabsf(region[1]) > fabsf(region[2]) ) - { - f32 py = vg_signf(region[1]) * bbx[1][1]; - v3_copy( (v3f){ bbx[0][0], py, bbx[0][2] }, quad[0] ); - v3_copy( (v3f){ bbx[1][0], py, bbx[0][2] }, quad[1] ); - v3_copy( (v3f){ bbx[1][0], py, bbx[1][2] }, quad[2] ); - v3_copy( (v3f){ bbx[0][0], py, bbx[1][2] }, quad[3] ); - } - else - { - f32 pz = vg_signf(region[2]) * bbx[1][2]; - v3_copy( (v3f){ bbx[0][0], bbx[0][1], pz }, quad[0] ); - v3_copy( (v3f){ bbx[1][0], bbx[0][1], pz }, quad[1] ); - v3_copy( (v3f){ bbx[1][0], bbx[1][1], pz }, quad[2] ); - v3_copy( (v3f){ bbx[0][0], bbx[1][1], pz }, quad[3] ); - } - - capsule_manifold manifold; - rb_capsule_manifold_init( &manifold ); - - v3f c0, c1; - closest_point_aabb( p0, bbx, c0 ); - closest_point_aabb( p1, bbx, c1 ); - - v3f d0, d1, da; - v3_sub( c0, p0, d0 ); - v3_sub( c1, p1, d1 ); - v3_sub( p1, p0, da ); - - /* TODO: ? */ - v3_normalize(d0); - v3_normalize(d1); - v3_normalize(da); - - if( v3_dot( da, d0 ) <= 0.01f ) - rb_capsule_manifold( p0, c0, 0.0f, r, &manifold ); - - if( v3_dot( da, d1 ) >= -0.01f ) - rb_capsule_manifold( p1, c1, 1.0f, r, &manifold ); - - for( i32 i=0; i<4; i++ ){ - i32 i0 = i, - i1 = (i+1)%4; - - v3f ca, cb; - f32 ta, tb; - closest_segment_segment( p0, p1, quad[i0], quad[i1], &ta, &tb, ca, cb ); - rb_capsule_manifold( ca, cb, ta, r, &manifold ); - } - - /* - * Create final contacts based on line manifold - */ - m3x3_mulv( mtxB, manifold.d0, manifold.d0 ); - m3x3_mulv( mtxB, manifold.d1, manifold.d1 ); - return rb_capsule__manifold_done( mtxA, ca, &manifold, buf ); -} - -static int rb_sphere__box( v3f coA, f32 ra, - m4x3f mtxB, m4x3f mtxB_inverse, boxf box, - rb_ct *buf ){ - v3f co, delta; - closest_point_obb( coA, box, mtxB, mtxB_inverse, co ); - v3_sub( coA, co, delta ); - - f32 d2 = v3_length2(delta); - - if( d2 <= ra*ra ){ - f32 d; - - rb_ct *ct = buf; - if( d2 <= 0.0001f ){ - v3f e, coB; - v3_sub( box[1], box[0], e ); - v3_muls( e, 0.5f, e ); - v3_add( box[0], e, coB ); - v3_sub( coA, coB, delta ); - - /* - * some extra testing is required to find the best axis to push the - * object back outside the box. Since there isnt a clear seperating - * vector already, especially on really high aspect boxes. - */ - f32 lx = v3_dot( mtxB[0], delta ), - ly = v3_dot( mtxB[1], delta ), - lz = v3_dot( mtxB[2], delta ), - px = e[0] - fabsf(lx), - py = e[1] - fabsf(ly), - pz = e[2] - fabsf(lz); - - if( px < py && px < pz ) v3_muls( mtxB[0], vg_signf(lx), ct->n ); - else if( py < pz ) v3_muls( mtxB[1], vg_signf(ly), ct->n ); - else v3_muls( mtxB[2], vg_signf(lz), ct->n ); - - v3_muladds( coA, ct->n, -ra, ct->co ); - ct->p = ra; - } - else{ - d = sqrtf(d2); - v3_muls( delta, 1.0f/d, ct->n ); - ct->p = ra-d; - v3_copy( co, ct->co ); - } - - ct->type = k_contact_type_default; - return 1; - } - else return 0; -} - -static int rb_sphere__sphere( v3f coA, f32 ra, - v3f coB, f32 rb, rb_ct *buf ){ - v3f delta; - v3_sub( coA, coB, delta ); - - f32 d2 = v3_length2(delta), - r = ra+rb; - - if( d2 < r*r ){ - f32 d = sqrtf(d2); - - rb_ct *ct = buf; - v3_muls( delta, 1.0f/d, ct->n ); - - v3f p0, p1; - v3_muladds( coA, ct->n,-ra, p0 ); - v3_muladds( coB, ct->n, rb, p1 ); - v3_add( p0, p1, ct->co ); - v3_muls( ct->co, 0.5f, ct->co ); - ct->type = k_contact_type_default; - ct->p = r-d; - return 1; - } - else return 0; -} - -static int rb_sphere__triangle( m4x3f mtxA, f32 r, - v3f tri[3], rb_ct *buf ){ - v3f delta, co; - enum contact_type type = closest_on_triangle_1( mtxA[3], tri, co ); - v3_sub( mtxA[3], co, delta ); - f32 d2 = v3_length2( delta ); - - if( d2 <= r*r ){ - rb_ct *ct = buf; - - v3f ab, ac, tn; - v3_sub( tri[2], tri[0], ab ); - v3_sub( tri[1], tri[0], ac ); - v3_cross( ac, ab, tn ); - v3_copy( tn, ct->n ); - - if( v3_length2( ct->n ) <= 0.00001f ){ -#ifdef RIGIDBODY_CRY_ABOUT_EVERYTHING - vg_error( "Zero area triangle!\n" ); -#endif - return 0; - } - - v3_normalize( ct->n ); - - f32 d = sqrtf(d2); - - v3_copy( co, ct->co ); - ct->type = type; - ct->p = r-d; - return 1; - } - - return 0; -} - -static int rb_capsule__triangle( m4x3f mtxA, rb_capsule *c, - v3f tri[3], rb_ct *buf ){ - v3f pc, p0w, p1w; - v3_muladds( mtxA[3], mtxA[1], -c->h*0.5f+c->r, p0w ); - v3_muladds( mtxA[3], mtxA[1], c->h*0.5f-c->r, p1w ); - - capsule_manifold manifold; - rb_capsule_manifold_init( &manifold ); - - v3f v0, v1, n; - v3_sub( tri[1], tri[0], v0 ); - v3_sub( tri[2], tri[0], v1 ); - v3_cross( v0, v1, n ); - - if( v3_length2( n ) <= 0.00001f ){ -#ifdef RIGIDBODY_CRY_ABOUT_EVERYTHING - vg_error( "Zero area triangle!\n" ); -#endif - return 0; - } - - v3_normalize( n ); - -#if 1 - /* deep penetration recovery. for when we clip through the triangles. so its - * not very 'correct' */ - f32 dist; - if( ray_tri( tri, p0w, mtxA[1], &dist, 1 ) ){ - f32 l = c->h - c->r*2.0f; - if( (dist >= 0.0f) && (dist < l) ){ - v3f co; - v3_muladds( p0w, mtxA[1], dist, co ); - vg_line_point( co, 0.02f, 0xffffff00 ); - - v3f d0, d1; - v3_sub( p0w, co, d0 ); - v3_sub( p1w, co, d1 ); - - f32 p = vg_minf( v3_dot( n, d0 ), v3_dot( n, d1 ) ) - c->r; - - rb_ct *ct = buf; - ct->p = -p; - ct->type = k_contact_type_default; - v3_copy( n, ct->n ); - v3_muladds( co, n, p, ct->co ); - - return 1; - } - } -#endif - - v3f c0, c1; - closest_on_triangle_1( p0w, tri, c0 ); - closest_on_triangle_1( p1w, tri, c1 ); - - v3f d0, d1, da; - v3_sub( c0, p0w, d0 ); - v3_sub( c1, p1w, d1 ); - v3_sub( p1w, p0w, da ); - - v3_normalize(d0); - v3_normalize(d1); - v3_normalize(da); - - /* the two balls at the ends */ - if( v3_dot( da, d0 ) <= 0.01f ) - rb_capsule_manifold( p0w, c0, 0.0f, c->r, &manifold ); - if( v3_dot( da, d1 ) >= -0.01f ) - rb_capsule_manifold( p1w, c1, 1.0f, c->r, &manifold ); - - /* the edges to edges */ - for( int i=0; i<3; i++ ){ - int i0 = i, - i1 = (i+1)%3; - - v3f ca, cb; - f32 ta, tb; - closest_segment_segment( p0w, p1w, tri[i0], tri[i1], &ta, &tb, ca, cb ); - rb_capsule_manifold( ca, cb, ta, c->r, &manifold ); - } - - int count = rb_capsule__manifold_done( mtxA, c, &manifold, buf ); - for( int i=0; i vg_list_size(rb_contact_buffer) ) - return 0; - - return 1; -} - -static rb_ct *rb_global_buffer( void ){ - return &rb_contact_buffer[ rb_contact_count ]; -} - -/* - * ----------------------------------------------------------------------------- - * Boolean shape overlap functions - * ----------------------------------------------------------------------------- - */ - -/* - * Project AABB, and triangle interval onto axis to check if they overlap - */ -static int rb_box_triangle_interval( v3f extent, v3f axis, v3f tri[3] ){ - float - - r = extent[0] * fabsf(axis[0]) + - extent[1] * fabsf(axis[1]) + - extent[2] * fabsf(axis[2]), - - p0 = v3_dot( axis, tri[0] ), - p1 = v3_dot( axis, tri[1] ), - p2 = v3_dot( axis, tri[2] ), - - e = vg_maxf(-vg_maxf(p0,vg_maxf(p1,p2)), vg_minf(p0,vg_minf(p1,p2))); - - if( e > r ) return 0; - else return 1; -} - -/* - * Seperating axis test box vs triangle - */ -static int rb_box_triangle_sat( v3f extent, v3f center, - m4x3f to_local, v3f tri_src[3] ){ - v3f tri[3]; - - for( int i=0; i<3; i++ ){ - m4x3_mulv( to_local, tri_src[i], tri[i] ); - v3_sub( tri[i], center, tri[i] ); - } - - v3f f0,f1,f2,n; - v3_sub( tri[1], tri[0], f0 ); - v3_sub( tri[2], tri[1], f1 ); - v3_sub( tri[0], tri[2], f2 ); - - - v3f axis[9]; - v3_cross( (v3f){1.0f,0.0f,0.0f}, f0, axis[0] ); - v3_cross( (v3f){1.0f,0.0f,0.0f}, f1, axis[1] ); - v3_cross( (v3f){1.0f,0.0f,0.0f}, f2, axis[2] ); - v3_cross( (v3f){0.0f,1.0f,0.0f}, f0, axis[3] ); - v3_cross( (v3f){0.0f,1.0f,0.0f}, f1, axis[4] ); - v3_cross( (v3f){0.0f,1.0f,0.0f}, f2, axis[5] ); - v3_cross( (v3f){0.0f,0.0f,1.0f}, f0, axis[6] ); - v3_cross( (v3f){0.0f,0.0f,1.0f}, f1, axis[7] ); - v3_cross( (v3f){0.0f,0.0f,1.0f}, f2, axis[8] ); - - for( int i=0; i<9; i++ ) - if(!rb_box_triangle_interval( extent, axis[i], tri )) return 0; - - /* u0, u1, u2 */ - if(!rb_box_triangle_interval( extent, (v3f){1.0f,0.0f,0.0f}, tri )) return 0; - if(!rb_box_triangle_interval( extent, (v3f){0.0f,1.0f,0.0f}, tri )) return 0; - if(!rb_box_triangle_interval( extent, (v3f){0.0f,0.0f,1.0f}, tri )) return 0; - - /* normal */ - v3_cross( f0, f1, n ); - if(!rb_box_triangle_interval( extent, n, tri )) return 0; - - return 1; -} - -/* - * ----------------------------------------------------------------------------- - * Manifold - * ----------------------------------------------------------------------------- - */ - -static int rb_manifold_apply_filtered( rb_ct *man, int len ){ - int k = 0; - - for( int i=0; itype == k_contact_type_disabled ) - continue; - - man[k ++] = man[i]; - } - - return k; -} +extern rb_contact_buffer[VG_MAX_CONTACTS]; +extern int rb_contact_count; + +int rb_capsule__sphere( m4x3f mtxA, rb_capsule *ca, + v3f coB, f32 rb, rb_ct *buf ); +int rb_capsule__capsule( m4x3f mtxA, rb_capsule *ca, + m4x3f mtxB, rb_capsule *cb, rb_ct *buf ); +int rb_capsule__box( m4x3f mtxA, rb_capsule *ca, + m4x3f mtxB, m4x3f mtxB_inverse, boxf box, + rb_ct *buf ); +int rb_sphere__box( v3f coA, f32 ra, + m4x3f mtxB, m4x3f mtxB_inverse, boxf box, + rb_ct *buf ); +int rb_sphere__sphere( v3f coA, f32 ra, v3f coB, f32 rb, rb_ct *buf ); +int rb_sphere__triangle( m4x3f mtxA, f32 r, v3f tri[3], rb_ct *buf ); +int rb_capsule__triangle( m4x3f mtxA, rb_capsule *c, v3f tri[3], rb_ct *buf ); +int rb_global_has_space( void ); +rb_ct *rb_global_buffer( void ); +int rb_manifold_apply_filtered( rb_ct *man, int len ); + +int rb_box_triangle_sat( v3f extent, v3f center, + m4x3f to_local, v3f tri_src[3] ); /* * Merge two contacts if they are within radius(r) of eachother */ -static void rb_manifold_contact_weld( rb_ct *ci, rb_ct *cj, float r ){ - if( v3_dist2( ci->co, cj->co ) < r*r ){ - cj->type = k_contact_type_disabled; - ci->p = (ci->p + cj->p) * 0.5f; - - v3_add( ci->co, cj->co, ci->co ); - v3_muls( ci->co, 0.5f, ci->co ); - - v3f delta; - v3_sub( ci->rba->co, ci->co, delta ); - - float c0 = v3_dot( ci->n, delta ), - c1 = v3_dot( cj->n, delta ); - - if( c0 < 0.0f || c1 < 0.0f ){ - /* error */ - ci->type = k_contact_type_disabled; - } - else{ - v3f n; - v3_muls( ci->n, c0, n ); - v3_muladds( n, cj->n, c1, n ); - v3_normalize( n ); - v3_copy( n, ci->n ); - } - } -} - -/* - * - */ -static void rb_manifold_filter_joint_edges( rb_ct *man, int len, float r ){ - for( int i=0; itype != k_contact_type_edge ) - continue; - - for( int j=i+1; jtype != k_contact_type_edge ) - continue; - - rb_manifold_contact_weld( ci, cj, r ); - } - } -} +void rb_manifold_contact_weld( rb_ct *ci, rb_ct *cj, float r ); +void rb_manifold_filter_joint_edges( rb_ct *man, int len, float r ); /* * Resolve overlapping pairs */ -static void rb_manifold_filter_pairs( rb_ct *man, int len, float r ){ - for( int i=0; itype == k_contact_type_disabled ) continue; - - for( int j=i+1; jtype == k_contact_type_disabled ) continue; - - if( v3_dist2( ci->co, cj->co ) < r*r ){ - cj->type = k_contact_type_disabled; - v3_add( cj->n, ci->n, ci->n ); - ci->p += cj->p; - similar ++; - } - } - - if( similar ){ - float n = 1.0f/((float)similar+1.0f); - v3_muls( ci->n, n, ci->n ); - ci->p *= n; - - if( v3_length2(ci->n) < 0.1f*0.1f ) - ci->type = k_contact_type_disabled; - else - v3_normalize( ci->n ); - } - } -} +void rb_manifold_filter_pairs( rb_ct *man, int len, float r ); /* * Remove contacts that are facing away from A */ -static void rb_manifold_filter_backface( rb_ct *man, int len ){ - for( int i=0; itype == k_contact_type_disabled ) - continue; - - v3f delta; - v3_sub( ct->co, ct->rba->co, delta ); - - if( v3_dot( delta, ct->n ) > -0.001f ) - ct->type = k_contact_type_disabled; - } -} +void rb_manifold_filter_backface( rb_ct *man, int len ); /* * Filter out duplicate coplanar results. Good for spheres. */ -static void rb_manifold_filter_coplanar( rb_ct *man, int len, float w ){ - for( int i=0; itype == k_contact_type_disabled || - ci->type == k_contact_type_edge ) - continue; - - float d1 = v3_dot( ci->co, ci->n ); - - for( int j=0; jtype == k_contact_type_disabled ) - continue; - - float d2 = v3_dot( cj->co, ci->n ), - d = d2-d1; - - if( fabsf( d ) <= w ){ - cj->type = k_contact_type_disabled; - } - } - } -} - -static void rb_debug_contact( rb_ct *ct ){ - v3f p1; - v3_muladds( ct->co, ct->n, 0.05f, p1 ); - - if( ct->type == k_contact_type_default ){ - vg_line_point( ct->co, 0.0125f, 0xff0000ff ); - vg_line( ct->co, p1, 0xffffffff ); - } - else if( ct->type == k_contact_type_edge ){ - vg_line_point( ct->co, 0.0125f, 0xff00ffc0 ); - vg_line( ct->co, p1, 0xffffffff ); - } -} - -static void rb_solver_reset(void){ - rb_contact_count = 0; -} - -static rb_ct *rb_global_ct(void){ - return rb_contact_buffer + rb_contact_count; -} - -static void rb_prepare_contact( rb_ct *ct ){ - ct->bias = -k_phys_baumgarte * (vg.time_fixed_delta*3600.0f) - * vg_minf( 0.0f, -ct->p+k_penetration_slop ); - - v3_tangent_basis( ct->n, ct->t[0], ct->t[1] ); - ct->norm_impulse = 0.0f; - ct->tangent_impulse[0] = 0.0f; - ct->tangent_impulse[1] = 0.0f; -} - -/* - * calculate total move to depenetrate object from contacts. - * manifold should belong to ONE object only - */ -static void rb_depenetrate( rb_ct *manifold, int len, v3f dt ){ - v3_zero( dt ); - - for( int j=0; j<7; j++ ){ - for( int i=0; in, dt ), - remaining = (ct->p-k_penetration_slop) - resolved_amt, - apply = vg_maxf( remaining, 0.0f ) * 0.4f; - - v3_muladds( dt, ct->n, apply, dt ); - } - } -} - -/* - * Initializing things like tangent vectors - */ -static void rb_presolve_contacts( rb_ct *buffer, int len ){ - for( int i=0; ico, ct->rba->co, ra ); - v3_sub( ct->co, ct->rbb->co, rb ); - v3_cross( ra, ct->n, raCn ); - v3_cross( rb, ct->n, rbCn ); - - /* orient inverse inertia tensors */ - v3f raCnI, rbCnI; - m3x3_mulv( ct->rba->iIw, raCn, raCnI ); - m3x3_mulv( ct->rbb->iIw, rbCn, rbCnI ); - - ct->normal_mass = ct->rba->inv_mass + ct->rbb->inv_mass; - ct->normal_mass += v3_dot( raCn, raCnI ); - ct->normal_mass += v3_dot( rbCn, rbCnI ); - ct->normal_mass = 1.0f/ct->normal_mass; - - for( int j=0; j<2; j++ ){ - v3f raCtI, rbCtI; - v3_cross( ct->t[j], ra, raCt ); - v3_cross( ct->t[j], rb, rbCt ); - m3x3_mulv( ct->rba->iIw, raCt, raCtI ); - m3x3_mulv( ct->rbb->iIw, rbCt, rbCtI ); - - ct->tangent_mass[j] = ct->rba->inv_mass + ct->rbb->inv_mass; - ct->tangent_mass[j] += v3_dot( raCt, raCtI ); - ct->tangent_mass[j] += v3_dot( rbCt, rbCtI ); - ct->tangent_mass[j] = 1.0f/ct->tangent_mass[j]; - } - } -} - -static void rb_contact_restitution( rb_ct *ct, float cr ){ - v3f rv, ra, rb; - v3_sub( ct->co, ct->rba->co, ra ); - v3_sub( ct->co, ct->rbb->co, rb ); - rb_rcv( ct->rba, ct->rbb, ra, rb, rv ); - - float v = v3_dot( rv, ct->n ); - - if( v < -1.0f ){ - ct->bias += -cr * v; - } -} - -/* - * One iteration to solve the contact constraint - */ -static void rb_solve_contacts( rb_ct *buf, int len ){ - for( int i=0; ico, ct->rba->co, ra ); - v3_sub( ct->co, ct->rbb->co, rb ); - rb_rcv( ct->rba, ct->rbb, ra, rb, rv ); - - /* Friction */ - for( int j=0; j<2; j++ ){ - float f = k_friction * ct->norm_impulse, - vt = v3_dot( rv, ct->t[j] ), - lambda = ct->tangent_mass[j] * -vt; - - float temp = ct->tangent_impulse[j]; - ct->tangent_impulse[j] = vg_clampf( temp + lambda, -f, f ); - lambda = ct->tangent_impulse[j] - temp; - - v3f impulse; - v3_muls( ct->t[j], lambda, impulse ); - rb_linear_impulse( ct->rba, ra, impulse ); - - v3_muls( ct->t[j], -lambda, impulse ); - rb_linear_impulse( ct->rbb, rb, impulse ); - } - - /* Normal */ - rb_rcv( ct->rba, ct->rbb, ra, rb, rv ); - float vn = v3_dot( rv, ct->n ), - lambda = ct->normal_mass * (-vn + ct->bias); - - float temp = ct->norm_impulse; - ct->norm_impulse = vg_maxf( temp + lambda, 0.0f ); - lambda = ct->norm_impulse - temp; - - v3f impulse; - v3_muls( ct->n, lambda, impulse ); - rb_linear_impulse( ct->rba, ra, impulse ); - - v3_muls( ct->n, -lambda, impulse ); - rb_linear_impulse( ct->rbb, rb, impulse ); - } -} +void rb_manifold_filter_coplanar( rb_ct *man, int len, float w ); +void rb_debug_contact( rb_ct *ct ); +void rb_solver_reset(void); +rb_ct *rb_global_ct(void); +void rb_prepare_contact( rb_ct *ct, f32 dt ); +void rb_depenetrate( rb_ct *manifold, int len, v3f dt ); +void rb_presolve_contacts( rb_ct *buffer, f32 dt, int len ); +void rb_contact_restitution( rb_ct *ct, float cr ); +void rb_solve_contacts( rb_ct *buf, int len ); diff --git a/vg_rigidbody_constraints.c b/vg_rigidbody_constraints.c new file mode 100644 index 0000000..3d6b932 --- /dev/null +++ b/vg_rigidbody_constraints.c @@ -0,0 +1,497 @@ +#pragma once +#include "vg_rigidbody.h" +#include "vg_rigidbody_constraints.h" +#include "vg_m.h" +#include "vg_lines.h" + +/* + * ----------------------------------------------------------------------------- + * Constraints + * ----------------------------------------------------------------------------- + */ + +void rb_debug_position_constraints( rb_constr_pos *buffer, int len ) +{ + for( int i=0; irba, *rbb = constr->rbb; + + v3f wca, wcb; + m3x3_mulv( rba->to_world, constr->lca, wca ); + m3x3_mulv( rbb->to_world, constr->lcb, wcb ); + + v3f p0, p1; + v3_add( wca, rba->co, p0 ); + v3_add( wcb, rbb->co, p1 ); + vg_line_point( p0, 0.0025f, 0xff000000 ); + vg_line_point( p1, 0.0025f, 0xffffffff ); + vg_line2( p0, p1, 0xff000000, 0xffffffff ); + } +} + +void rb_presolve_swingtwist_constraints( rb_constr_swingtwist *buf, int len ) +{ + for( int i=0; irba->to_world, st->conevx, vx ); + m3x3_mulv( st->rbb->to_world, st->conevxb, vxb ); + m3x3_mulv( st->rba->to_world, st->conevy, vy ); + m3x3_mulv( st->rbb->to_world, st->coneva, va ); + m4x3_mulv( st->rba->to_world, st->view_offset, center ); + v3_cross( vy, vx, axis ); + + /* Constraint violated ? */ + float fx = v3_dot( vx, va ), /* projection world */ + fy = v3_dot( vy, va ), + fn = v3_dot( va, axis ), + + rx = st->conevx[3], /* elipse radii */ + ry = st->conevy[3], + + lx = fx/rx, /* projection local (fn==lz) */ + ly = fy/ry; + + st->tangent_violation = ((lx*lx + ly*ly) > fn*fn) || (fn <= 0.0f); + if( st->tangent_violation ){ + /* Calculate a good position and the axis to solve on */ + v2f closest, tangent, + p = { fx/fabsf(fn), fy/fabsf(fn) }; + + closest_point_elipse( p, (v2f){rx,ry}, closest ); + tangent[0] = -closest[1] / (ry*ry); + tangent[1] = closest[0] / (rx*rx); + v2_normalize( tangent ); + + v3f v0, v1; + v3_muladds( axis, vx, closest[0], v0 ); + v3_muladds( v0, vy, closest[1], v0 ); + v3_normalize( v0 ); + + v3_muls( vx, tangent[0], v1 ); + v3_muladds( v1, vy, tangent[1], v1 ); + + v3_copy( v0, st->tangent_target ); + v3_copy( v1, st->tangent_axis ); + + /* calculate mass */ + v3f aIw, bIw; + m3x3_mulv( st->rba->iIw, st->tangent_axis, aIw ); + m3x3_mulv( st->rbb->iIw, st->tangent_axis, bIw ); + st->tangent_mass = 1.0f / (v3_dot( st->tangent_axis, aIw ) + + v3_dot( st->tangent_axis, bIw )); + + float angle = v3_dot( va, st->tangent_target ); + } + + v3f refaxis; + v3_cross( vy, va, refaxis ); /* our default rotation */ + v3_normalize( refaxis ); + + float angle = v3_dot( refaxis, vxb ); + st->axis_violation = fabsf(angle) < st->conet; + + if( st->axis_violation ){ + v3f dir_test; + v3_cross( refaxis, vxb, dir_test ); + + if( v3_dot(dir_test, va) < 0.0f ) + st->axis_violation = -st->axis_violation; + + float newang = (float)st->axis_violation * acosf(st->conet-0.0001f); + + v3f refaxis_up; + v3_cross( va, refaxis, refaxis_up ); + v3_muls( refaxis_up, sinf(newang), st->axis_target ); + v3_muladds( st->axis_target, refaxis, -cosf(newang), st->axis_target ); + + /* calculate mass */ + v3_copy( va, st->axis ); + v3f aIw, bIw; + m3x3_mulv( st->rba->iIw, st->axis, aIw ); + m3x3_mulv( st->rbb->iIw, st->axis, bIw ); + st->axis_mass = 1.0f / (v3_dot( st->axis, aIw ) + + v3_dot( st->axis, bIw )); + } + } +} + +void rb_debug_swingtwist_constraints( rb_constr_swingtwist *buf, int len ) +{ + float size = 0.12f; + + for( int i=0; irba->to_world, st->conevx, vx ); + m3x3_mulv( st->rbb->to_world, st->conevxb, vxb ); + m3x3_mulv( st->rba->to_world, st->conevy, vy ); + m3x3_mulv( st->rbb->to_world, st->coneva, va ); + m4x3_mulv( st->rba->to_world, st->view_offset, center ); + v3_cross( vy, vx, axis ); + + float rx = st->conevx[3], /* elipse radii */ + ry = st->conevy[3]; + + v3f p0, p1; + v3_muladds( center, va, size, p1 ); + vg_line( center, p1, 0xffffffff ); + vg_line_point( p1, 0.00025f, 0xffffffff ); + + if( st->tangent_violation ){ + v3_muladds( center, st->tangent_target, size, p0 ); + + vg_line( center, p0, 0xff00ff00 ); + vg_line_point( p0, 0.00025f, 0xff00ff00 ); + vg_line( p1, p0, 0xff000000 ); + } + + for( int x=0; x<32; x++ ){ + float t0 = ((float)x * (1.0f/32.0f)) * VG_TAUf, + t1 = (((float)x+1.0f) * (1.0f/32.0f)) * VG_TAUf, + c0 = cosf( t0 ), + s0 = sinf( t0 ), + c1 = cosf( t1 ), + s1 = sinf( t1 ); + + v3f v0, v1; + v3_muladds( axis, vx, c0*rx, v0 ); + v3_muladds( v0, vy, s0*ry, v0 ); + v3_muladds( axis, vx, c1*rx, v1 ); + v3_muladds( v1, vy, s1*ry, v1 ); + + v3_normalize( v0 ); + v3_normalize( v1 ); + + v3_muladds( center, v0, size, p0 ); + v3_muladds( center, v1, size, p1 ); + + u32 col0r = fabsf(c0) * 255.0f, + col0g = fabsf(s0) * 255.0f, + col1r = fabsf(c1) * 255.0f, + col1g = fabsf(s1) * 255.0f, + col = st->tangent_violation? 0xff0000ff: 0xff000000, + col0 = col | (col0r<<16) | (col0g << 8), + col1 = col | (col1r<<16) | (col1g << 8); + + vg_line2( center, p0, VG__NONE, col0 ); + vg_line2( p0, p1, col0, col1 ); + } + + /* Draw twist */ + v3_muladds( center, va, size, p0 ); + v3_muladds( p0, vxb, size, p1 ); + + vg_line( p0, p1, 0xff0000ff ); + + if( st->axis_violation ){ + v3_muladds( p0, st->axis_target, size*1.25f, p1 ); + vg_line( p0, p1, 0xffffff00 ); + vg_line_point( p1, 0.0025f, 0xffffff80 ); + } + + v3f refaxis; + v3_cross( vy, va, refaxis ); /* our default rotation */ + v3_normalize( refaxis ); + v3f refaxis_up; + v3_cross( va, refaxis, refaxis_up ); + float newang = acosf(st->conet-0.0001f); + + v3_muladds( p0, refaxis_up, sinf(newang)*size, p1 ); + v3_muladds( p1, refaxis, -cosf(newang)*size, p1 ); + vg_line( p0, p1, 0xff000000 ); + + v3_muladds( p0, refaxis_up, sinf(-newang)*size, p1 ); + v3_muladds( p1, refaxis, -cosf(-newang)*size, p1 ); + vg_line( p0, p1, 0xff404040 ); + } +} + +void rb_solve_position_constraints( rb_constr_pos *buf, int len ) +{ + for( int i=0; irba, *rbb = constr->rbb; + + v3f wa, wb; + m3x3_mulv( rba->to_world, constr->lca, wa ); + m3x3_mulv( rbb->to_world, constr->lcb, wb ); + + m3x3f ssra, ssrat, ssrb, ssrbt; + + m3x3_skew_symetric( ssrat, wa ); + m3x3_skew_symetric( ssrbt, wb ); + m3x3_transpose( ssrat, ssra ); + m3x3_transpose( ssrbt, ssrb ); + + v3f b, b_wa, b_wb, b_a, b_b; + m3x3_mulv( ssra, rba->w, b_wa ); + m3x3_mulv( ssrb, rbb->w, b_wb ); + v3_add( rba->v, b_wa, b ); + v3_sub( b, rbb->v, b ); + v3_sub( b, b_wb, b ); + v3_muls( b, -1.0f, b ); + + m3x3f invMa, invMb; + m3x3_diagonal( invMa, rba->inv_mass ); + m3x3_diagonal( invMb, rbb->inv_mass ); + + m3x3f ia, ib; + m3x3_mul( ssra, rba->iIw, ia ); + m3x3_mul( ia, ssrat, ia ); + m3x3_mul( ssrb, rbb->iIw, ib ); + m3x3_mul( ib, ssrbt, ib ); + + m3x3f cma, cmb; + m3x3_add( invMa, ia, cma ); + m3x3_add( invMb, ib, cmb ); + + m3x3f A; + m3x3_add( cma, cmb, A ); + + /* Solve Ax = b ( A^-1*b = x ) */ + v3f impulse; + m3x3f invA; + m3x3_inv( A, invA ); + m3x3_mulv( invA, b, impulse ); + + v3f delta_va, delta_wa, delta_vb, delta_wb; + m3x3f iwa, iwb; + m3x3_mul( rba->iIw, ssrat, iwa ); + m3x3_mul( rbb->iIw, ssrbt, iwb ); + + m3x3_mulv( invMa, impulse, delta_va ); + m3x3_mulv( invMb, impulse, delta_vb ); + m3x3_mulv( iwa, impulse, delta_wa ); + m3x3_mulv( iwb, impulse, delta_wb ); + + v3_add( rba->v, delta_va, rba->v ); + v3_add( rba->w, delta_wa, rba->w ); + v3_sub( rbb->v, delta_vb, rbb->v ); + v3_sub( rbb->w, delta_wb, rbb->w ); + } +} + +void rb_solve_swingtwist_constraints( rb_constr_swingtwist *buf, int len ) +{ + for( int i=0; iaxis_violation ) + continue; + + float rv = v3_dot( st->axis, st->rbb->w ) - + v3_dot( st->axis, st->rba->w ); + + if( rv * (float)st->axis_violation > 0.0f ) + continue; + + v3f impulse, wa, wb; + v3_muls( st->axis, rv*st->axis_mass, impulse ); + m3x3_mulv( st->rba->iIw, impulse, wa ); + v3_add( st->rba->w, wa, st->rba->w ); + + v3_muls( impulse, -1.0f, impulse ); + m3x3_mulv( st->rbb->iIw, impulse, wb ); + v3_add( st->rbb->w, wb, st->rbb->w ); + + float rv2 = v3_dot( st->axis, st->rbb->w ) - + v3_dot( st->axis, st->rba->w ); + } + + for( int i=0; itangent_violation ) + continue; + + float rv = v3_dot( st->tangent_axis, st->rbb->w ) - + v3_dot( st->tangent_axis, st->rba->w ); + + if( rv > 0.0f ) + continue; + + v3f impulse, wa, wb; + v3_muls( st->tangent_axis, rv*st->tangent_mass, impulse ); + m3x3_mulv( st->rba->iIw, impulse, wa ); + v3_add( st->rba->w, wa, st->rba->w ); + + v3_muls( impulse, -1.0f, impulse ); + m3x3_mulv( st->rbb->iIw, impulse, wb ); + v3_add( st->rbb->w, wb, st->rbb->w ); + + float rv2 = v3_dot( st->tangent_axis, st->rbb->w ) - + v3_dot( st->tangent_axis, st->rba->w ); + } +} + +/* debugging */ +void rb_postsolve_swingtwist_constraints( rb_constr_swingtwist *buf, u32 len ) +{ + for( int i=0; iaxis_violation ){ + st->conv_axis = 0.0f; + continue; + } + + f32 rv = v3_dot( st->axis, st->rbb->w ) - + v3_dot( st->axis, st->rba->w ); + + if( rv * (f32)st->axis_violation > 0.0f ) + st->conv_axis = 0.0f; + else + st->conv_axis = rv; + } + + for( int i=0; itangent_violation ){ + st->conv_tangent = 0.0f; + continue; + } + + f32 rv = v3_dot( st->tangent_axis, st->rbb->w ) - + v3_dot( st->tangent_axis, st->rba->w ); + + if( rv > 0.0f ) + st->conv_tangent = 0.0f; + else + st->conv_tangent = rv; + } +} + +void rb_solve_constr_angle( rigidbody *rba, rigidbody *rbb, v3f ra, v3f rb ) +{ + m3x3f ssra, ssrb, ssrat, ssrbt; + m3x3f cma, cmb; + + m3x3_skew_symetric( ssrat, ra ); + m3x3_skew_symetric( ssrbt, rb ); + m3x3_transpose( ssrat, ssra ); + m3x3_transpose( ssrbt, ssrb ); + + m3x3_mul( ssra, rba->iIw, cma ); + m3x3_mul( cma, ssrat, cma ); + m3x3_mul( ssrb, rbb->iIw, cmb ); + m3x3_mul( cmb, ssrbt, cmb ); + + m3x3f A, invA; + m3x3_add( cma, cmb, A ); + m3x3_inv( A, invA ); + + v3f b_wa, b_wb, b; + m3x3_mulv( ssra, rba->w, b_wa ); + m3x3_mulv( ssrb, rbb->w, b_wb ); + v3_add( b_wa, b_wb, b ); + v3_negate( b, b ); + + v3f impulse; + m3x3_mulv( invA, b, impulse ); + + v3f delta_wa, delta_wb; + m3x3f iwa, iwb; + m3x3_mul( rba->iIw, ssrat, iwa ); + m3x3_mul( rbb->iIw, ssrbt, iwb ); + m3x3_mulv( iwa, impulse, delta_wa ); + m3x3_mulv( iwb, impulse, delta_wb ); + v3_add( rba->w, delta_wa, rba->w ); + v3_sub( rbb->w, delta_wb, rbb->w ); +} + +void rb_correct_position_constraints( rb_constr_pos *buf, int len, f32 amt ) +{ + for( int i=0; irba, *rbb = constr->rbb; + + v3f p0, p1, d; + m3x3_mulv( rba->to_world, constr->lca, p0 ); + m3x3_mulv( rbb->to_world, constr->lcb, p1 ); + v3_add( rba->co, p0, p0 ); + v3_add( rbb->co, p1, p1 ); + v3_sub( p1, p0, d ); + +#if 1 + v3_muladds( rbb->co, d, -1.0f * amt, rbb->co ); + rb_update_matrices( rbb ); +#else + f32 mt = 1.0f/(rba->inv_mass+rbb->inv_mass), + a = mt * (k_phys_baumgarte/k_rb_delta); + + v3_muladds( rba->v, d, a* rba->inv_mass, rba->v ); + v3_muladds( rbb->v, d, a*-rbb->inv_mass, rbb->v ); +#endif + } +} + +void rb_correct_swingtwist_constraints( rb_constr_swingtwist *buf, + int len, float amt ) +{ + for( int i=0; itangent_violation ) + continue; + + v3f va; + m3x3_mulv( st->rbb->to_world, st->coneva, va ); + + f32 angle = v3_dot( va, st->tangent_target ); + + if( fabsf(angle) < 0.9999f ){ + v3f axis; + v3_cross( va, st->tangent_target, axis ); +#if 1 + angle = acosf(angle) * amt; + v4f correction; + q_axis_angle( correction, axis, angle ); + q_mul( correction, st->rbb->q, st->rbb->q ); + q_normalize( st->rbb->q ); + rb_update_matrices( st->rbb ); +#else + f32 mt = 1.0f/(st->rba->inv_mass+st->rbb->inv_mass), + wa = mt * acosf(angle) * (k_phys_baumgarte/k_rb_delta); + //v3_muladds( st->rba->w, axis, wa*-st->rba->inv_mass, st->rba->w ); + v3_muladds( st->rbb->w, axis, wa* st->rbb->inv_mass, st->rbb->w ); +#endif + } + } + + for( int i=0; iaxis_violation ) + continue; + + v3f vxb; + m3x3_mulv( st->rbb->to_world, st->conevxb, vxb ); + + f32 angle = v3_dot( vxb, st->axis_target ); + + if( fabsf(angle) < 0.9999f ){ + v3f axis; + v3_cross( vxb, st->axis_target, axis ); + +#if 1 + angle = acosf(angle) * amt; + v4f correction; + q_axis_angle( correction, axis, angle ); + q_mul( correction, st->rbb->q, st->rbb->q ); + q_normalize( st->rbb->q ); + rb_update_matrices( st->rbb ); +#else + f32 mt = 1.0f/(st->rba->inv_mass+st->rbb->inv_mass), + wa = mt * acosf(angle) * (k_phys_baumgarte/k_rb_delta); + //v3_muladds( st->rba->w, axis, wa*-0.5f, st->rba->w ); + v3_muladds( st->rbb->w, axis, wa* st->rbb->inv_mass, st->rbb->w ); +#endif + } + } +} diff --git a/vg_rigidbody_constraints.h b/vg_rigidbody_constraints.h index 410b14f..92ec26d 100644 --- a/vg_rigidbody_constraints.h +++ b/vg_rigidbody_constraints.h @@ -1,15 +1,16 @@ #pragma once -#include "vg_rigidbody.h" typedef struct rb_constr_pos rb_constr_pos; typedef struct rb_constr_swingtwist rb_constr_swingtwist; -struct rb_constr_pos{ +struct rb_constr_pos +{ rigidbody *rba, *rbb; v3f lca, lcb; }; -struct rb_constr_swingtwist{ +struct rb_constr_swingtwist +{ rigidbody *rba, *rbb; v4f conevx, conevy; /* relative to rba */ @@ -25,498 +26,22 @@ struct rb_constr_swingtwist{ f32 conv_tangent, conv_axis; }; -/* - * ----------------------------------------------------------------------------- - * Constraints - * ----------------------------------------------------------------------------- - */ - -static void rb_debug_position_constraints( rb_constr_pos *buffer, int len ){ - for( int i=0; irba, *rbb = constr->rbb; - - v3f wca, wcb; - m3x3_mulv( rba->to_world, constr->lca, wca ); - m3x3_mulv( rbb->to_world, constr->lcb, wcb ); - - v3f p0, p1; - v3_add( wca, rba->co, p0 ); - v3_add( wcb, rbb->co, p1 ); - vg_line_point( p0, 0.0025f, 0xff000000 ); - vg_line_point( p1, 0.0025f, 0xffffffff ); - vg_line2( p0, p1, 0xff000000, 0xffffffff ); - } -} - -static void rb_presolve_swingtwist_constraints( rb_constr_swingtwist *buf, - int len ){ - for( int i=0; irba->to_world, st->conevx, vx ); - m3x3_mulv( st->rbb->to_world, st->conevxb, vxb ); - m3x3_mulv( st->rba->to_world, st->conevy, vy ); - m3x3_mulv( st->rbb->to_world, st->coneva, va ); - m4x3_mulv( st->rba->to_world, st->view_offset, center ); - v3_cross( vy, vx, axis ); - - /* Constraint violated ? */ - float fx = v3_dot( vx, va ), /* projection world */ - fy = v3_dot( vy, va ), - fn = v3_dot( va, axis ), - - rx = st->conevx[3], /* elipse radii */ - ry = st->conevy[3], - - lx = fx/rx, /* projection local (fn==lz) */ - ly = fy/ry; - - st->tangent_violation = ((lx*lx + ly*ly) > fn*fn) || (fn <= 0.0f); - if( st->tangent_violation ){ - /* Calculate a good position and the axis to solve on */ - v2f closest, tangent, - p = { fx/fabsf(fn), fy/fabsf(fn) }; - - closest_point_elipse( p, (v2f){rx,ry}, closest ); - tangent[0] = -closest[1] / (ry*ry); - tangent[1] = closest[0] / (rx*rx); - v2_normalize( tangent ); - - v3f v0, v1; - v3_muladds( axis, vx, closest[0], v0 ); - v3_muladds( v0, vy, closest[1], v0 ); - v3_normalize( v0 ); - - v3_muls( vx, tangent[0], v1 ); - v3_muladds( v1, vy, tangent[1], v1 ); - - v3_copy( v0, st->tangent_target ); - v3_copy( v1, st->tangent_axis ); - - /* calculate mass */ - v3f aIw, bIw; - m3x3_mulv( st->rba->iIw, st->tangent_axis, aIw ); - m3x3_mulv( st->rbb->iIw, st->tangent_axis, bIw ); - st->tangent_mass = 1.0f / (v3_dot( st->tangent_axis, aIw ) + - v3_dot( st->tangent_axis, bIw )); - - float angle = v3_dot( va, st->tangent_target ); - } - - v3f refaxis; - v3_cross( vy, va, refaxis ); /* our default rotation */ - v3_normalize( refaxis ); - - float angle = v3_dot( refaxis, vxb ); - st->axis_violation = fabsf(angle) < st->conet; - - if( st->axis_violation ){ - v3f dir_test; - v3_cross( refaxis, vxb, dir_test ); - - if( v3_dot(dir_test, va) < 0.0f ) - st->axis_violation = -st->axis_violation; - - float newang = (float)st->axis_violation * acosf(st->conet-0.0001f); - - v3f refaxis_up; - v3_cross( va, refaxis, refaxis_up ); - v3_muls( refaxis_up, sinf(newang), st->axis_target ); - v3_muladds( st->axis_target, refaxis, -cosf(newang), st->axis_target ); - - /* calculate mass */ - v3_copy( va, st->axis ); - v3f aIw, bIw; - m3x3_mulv( st->rba->iIw, st->axis, aIw ); - m3x3_mulv( st->rbb->iIw, st->axis, bIw ); - st->axis_mass = 1.0f / (v3_dot( st->axis, aIw ) + - v3_dot( st->axis, bIw )); - } - } -} - -static void rb_debug_swingtwist_constraints( rb_constr_swingtwist *buf, - int len ){ - float size = 0.12f; - - for( int i=0; irba->to_world, st->conevx, vx ); - m3x3_mulv( st->rbb->to_world, st->conevxb, vxb ); - m3x3_mulv( st->rba->to_world, st->conevy, vy ); - m3x3_mulv( st->rbb->to_world, st->coneva, va ); - m4x3_mulv( st->rba->to_world, st->view_offset, center ); - v3_cross( vy, vx, axis ); - - float rx = st->conevx[3], /* elipse radii */ - ry = st->conevy[3]; - - v3f p0, p1; - v3_muladds( center, va, size, p1 ); - vg_line( center, p1, 0xffffffff ); - vg_line_point( p1, 0.00025f, 0xffffffff ); - - if( st->tangent_violation ){ - v3_muladds( center, st->tangent_target, size, p0 ); - - vg_line( center, p0, 0xff00ff00 ); - vg_line_point( p0, 0.00025f, 0xff00ff00 ); - vg_line( p1, p0, 0xff000000 ); - } - - for( int x=0; x<32; x++ ){ - float t0 = ((float)x * (1.0f/32.0f)) * VG_TAUf, - t1 = (((float)x+1.0f) * (1.0f/32.0f)) * VG_TAUf, - c0 = cosf( t0 ), - s0 = sinf( t0 ), - c1 = cosf( t1 ), - s1 = sinf( t1 ); - - v3f v0, v1; - v3_muladds( axis, vx, c0*rx, v0 ); - v3_muladds( v0, vy, s0*ry, v0 ); - v3_muladds( axis, vx, c1*rx, v1 ); - v3_muladds( v1, vy, s1*ry, v1 ); - - v3_normalize( v0 ); - v3_normalize( v1 ); - - v3_muladds( center, v0, size, p0 ); - v3_muladds( center, v1, size, p1 ); - - u32 col0r = fabsf(c0) * 255.0f, - col0g = fabsf(s0) * 255.0f, - col1r = fabsf(c1) * 255.0f, - col1g = fabsf(s1) * 255.0f, - col = st->tangent_violation? 0xff0000ff: 0xff000000, - col0 = col | (col0r<<16) | (col0g << 8), - col1 = col | (col1r<<16) | (col1g << 8); - - vg_line2( center, p0, VG__NONE, col0 ); - vg_line2( p0, p1, col0, col1 ); - } - - /* Draw twist */ - v3_muladds( center, va, size, p0 ); - v3_muladds( p0, vxb, size, p1 ); - - vg_line( p0, p1, 0xff0000ff ); - - if( st->axis_violation ){ - v3_muladds( p0, st->axis_target, size*1.25f, p1 ); - vg_line( p0, p1, 0xffffff00 ); - vg_line_point( p1, 0.0025f, 0xffffff80 ); - } - - v3f refaxis; - v3_cross( vy, va, refaxis ); /* our default rotation */ - v3_normalize( refaxis ); - v3f refaxis_up; - v3_cross( va, refaxis, refaxis_up ); - float newang = acosf(st->conet-0.0001f); - - v3_muladds( p0, refaxis_up, sinf(newang)*size, p1 ); - v3_muladds( p1, refaxis, -cosf(newang)*size, p1 ); - vg_line( p0, p1, 0xff000000 ); - - v3_muladds( p0, refaxis_up, sinf(-newang)*size, p1 ); - v3_muladds( p1, refaxis, -cosf(-newang)*size, p1 ); - vg_line( p0, p1, 0xff404040 ); - } -} - +void rb_debug_position_constraints( rb_constr_pos *buffer, int len ); +void rb_presolve_swingtwist_constraints( rb_constr_swingtwist *buf, int len ); +void rb_debug_swingtwist_constraints( rb_constr_swingtwist *buf, int len ); + /* * Solve a list of positional constraints */ -static void rb_solve_position_constraints( rb_constr_pos *buf, int len ){ - for( int i=0; irba, *rbb = constr->rbb; - - v3f wa, wb; - m3x3_mulv( rba->to_world, constr->lca, wa ); - m3x3_mulv( rbb->to_world, constr->lcb, wb ); - - m3x3f ssra, ssrat, ssrb, ssrbt; - - m3x3_skew_symetric( ssrat, wa ); - m3x3_skew_symetric( ssrbt, wb ); - m3x3_transpose( ssrat, ssra ); - m3x3_transpose( ssrbt, ssrb ); - - v3f b, b_wa, b_wb, b_a, b_b; - m3x3_mulv( ssra, rba->w, b_wa ); - m3x3_mulv( ssrb, rbb->w, b_wb ); - v3_add( rba->v, b_wa, b ); - v3_sub( b, rbb->v, b ); - v3_sub( b, b_wb, b ); - v3_muls( b, -1.0f, b ); - - m3x3f invMa, invMb; - m3x3_diagonal( invMa, rba->inv_mass ); - m3x3_diagonal( invMb, rbb->inv_mass ); - - m3x3f ia, ib; - m3x3_mul( ssra, rba->iIw, ia ); - m3x3_mul( ia, ssrat, ia ); - m3x3_mul( ssrb, rbb->iIw, ib ); - m3x3_mul( ib, ssrbt, ib ); - - m3x3f cma, cmb; - m3x3_add( invMa, ia, cma ); - m3x3_add( invMb, ib, cmb ); - - m3x3f A; - m3x3_add( cma, cmb, A ); - - /* Solve Ax = b ( A^-1*b = x ) */ - v3f impulse; - m3x3f invA; - m3x3_inv( A, invA ); - m3x3_mulv( invA, b, impulse ); - - v3f delta_va, delta_wa, delta_vb, delta_wb; - m3x3f iwa, iwb; - m3x3_mul( rba->iIw, ssrat, iwa ); - m3x3_mul( rbb->iIw, ssrbt, iwb ); - - m3x3_mulv( invMa, impulse, delta_va ); - m3x3_mulv( invMb, impulse, delta_vb ); - m3x3_mulv( iwa, impulse, delta_wa ); - m3x3_mulv( iwb, impulse, delta_wb ); - - v3_add( rba->v, delta_va, rba->v ); - v3_add( rba->w, delta_wa, rba->w ); - v3_sub( rbb->v, delta_vb, rbb->v ); - v3_sub( rbb->w, delta_wb, rbb->w ); - } -} - -static void rb_solve_swingtwist_constraints( rb_constr_swingtwist *buf, - int len ){ - for( int i=0; iaxis_violation ) - continue; - - float rv = v3_dot( st->axis, st->rbb->w ) - - v3_dot( st->axis, st->rba->w ); - - if( rv * (float)st->axis_violation > 0.0f ) - continue; - - v3f impulse, wa, wb; - v3_muls( st->axis, rv*st->axis_mass, impulse ); - m3x3_mulv( st->rba->iIw, impulse, wa ); - v3_add( st->rba->w, wa, st->rba->w ); - - v3_muls( impulse, -1.0f, impulse ); - m3x3_mulv( st->rbb->iIw, impulse, wb ); - v3_add( st->rbb->w, wb, st->rbb->w ); - - float rv2 = v3_dot( st->axis, st->rbb->w ) - - v3_dot( st->axis, st->rba->w ); - } - - for( int i=0; itangent_violation ) - continue; - - float rv = v3_dot( st->tangent_axis, st->rbb->w ) - - v3_dot( st->tangent_axis, st->rba->w ); - - if( rv > 0.0f ) - continue; - - v3f impulse, wa, wb; - v3_muls( st->tangent_axis, rv*st->tangent_mass, impulse ); - m3x3_mulv( st->rba->iIw, impulse, wa ); - v3_add( st->rba->w, wa, st->rba->w ); - - v3_muls( impulse, -1.0f, impulse ); - m3x3_mulv( st->rbb->iIw, impulse, wb ); - v3_add( st->rbb->w, wb, st->rbb->w ); - - float rv2 = v3_dot( st->tangent_axis, st->rbb->w ) - - v3_dot( st->tangent_axis, st->rba->w ); - } -} - -/* debugging */ -static void rb_postsolve_swingtwist_constraints( rb_constr_swingtwist *buf, - u32 len ){ - for( int i=0; iaxis_violation ){ - st->conv_axis = 0.0f; - continue; - } - - f32 rv = v3_dot( st->axis, st->rbb->w ) - - v3_dot( st->axis, st->rba->w ); - - if( rv * (f32)st->axis_violation > 0.0f ) - st->conv_axis = 0.0f; - else - st->conv_axis = rv; - } - - for( int i=0; itangent_violation ){ - st->conv_tangent = 0.0f; - continue; - } - - f32 rv = v3_dot( st->tangent_axis, st->rbb->w ) - - v3_dot( st->tangent_axis, st->rba->w ); - - if( rv > 0.0f ) - st->conv_tangent = 0.0f; - else - st->conv_tangent = rv; - } -} - -static void rb_solve_constr_angle( rigidbody *rba, rigidbody *rbb, - v3f ra, v3f rb ){ - m3x3f ssra, ssrb, ssrat, ssrbt; - m3x3f cma, cmb; - - m3x3_skew_symetric( ssrat, ra ); - m3x3_skew_symetric( ssrbt, rb ); - m3x3_transpose( ssrat, ssra ); - m3x3_transpose( ssrbt, ssrb ); - - m3x3_mul( ssra, rba->iIw, cma ); - m3x3_mul( cma, ssrat, cma ); - m3x3_mul( ssrb, rbb->iIw, cmb ); - m3x3_mul( cmb, ssrbt, cmb ); - - m3x3f A, invA; - m3x3_add( cma, cmb, A ); - m3x3_inv( A, invA ); - - v3f b_wa, b_wb, b; - m3x3_mulv( ssra, rba->w, b_wa ); - m3x3_mulv( ssrb, rbb->w, b_wb ); - v3_add( b_wa, b_wb, b ); - v3_negate( b, b ); - - v3f impulse; - m3x3_mulv( invA, b, impulse ); - - v3f delta_wa, delta_wb; - m3x3f iwa, iwb; - m3x3_mul( rba->iIw, ssrat, iwa ); - m3x3_mul( rbb->iIw, ssrbt, iwb ); - m3x3_mulv( iwa, impulse, delta_wa ); - m3x3_mulv( iwb, impulse, delta_wb ); - v3_add( rba->w, delta_wa, rba->w ); - v3_sub( rbb->w, delta_wb, rbb->w ); -} +void rb_solve_position_constraints( rb_constr_pos *buf, int len ); +void rb_solve_swingtwist_constraints( rb_constr_swingtwist *buf, int len ); +void rb_postsolve_swingtwist_constraints( rb_constr_swingtwist *buf, u32 len ); +void rb_solve_constr_angle( rigidbody *rba, rigidbody *rbb, v3f ra, v3f rb ); /* * Correct position constraint drift errors * [ 0.0 <= amt <= 1.0 ]: the correction amount */ -static void rb_correct_position_constraints( rb_constr_pos *buf, int len, - float amt ){ - for( int i=0; irba, *rbb = constr->rbb; - - v3f p0, p1, d; - m3x3_mulv( rba->to_world, constr->lca, p0 ); - m3x3_mulv( rbb->to_world, constr->lcb, p1 ); - v3_add( rba->co, p0, p0 ); - v3_add( rbb->co, p1, p1 ); - v3_sub( p1, p0, d ); - -#if 1 - v3_muladds( rbb->co, d, -1.0f * amt, rbb->co ); - rb_update_matrices( rbb ); -#else - f32 mt = 1.0f/(rba->inv_mass+rbb->inv_mass), - a = mt * (k_phys_baumgarte/k_rb_delta); - - v3_muladds( rba->v, d, a* rba->inv_mass, rba->v ); - v3_muladds( rbb->v, d, a*-rbb->inv_mass, rbb->v ); -#endif - } -} - -static void rb_correct_swingtwist_constraints( rb_constr_swingtwist *buf, - int len, float amt ){ - for( int i=0; itangent_violation ) - continue; - - v3f va; - m3x3_mulv( st->rbb->to_world, st->coneva, va ); - - f32 angle = v3_dot( va, st->tangent_target ); - - if( fabsf(angle) < 0.9999f ){ - v3f axis; - v3_cross( va, st->tangent_target, axis ); -#if 1 - angle = acosf(angle) * amt; - v4f correction; - q_axis_angle( correction, axis, angle ); - q_mul( correction, st->rbb->q, st->rbb->q ); - q_normalize( st->rbb->q ); - rb_update_matrices( st->rbb ); -#else - f32 mt = 1.0f/(st->rba->inv_mass+st->rbb->inv_mass), - wa = mt * acosf(angle) * (k_phys_baumgarte/k_rb_delta); - //v3_muladds( st->rba->w, axis, wa*-st->rba->inv_mass, st->rba->w ); - v3_muladds( st->rbb->w, axis, wa* st->rbb->inv_mass, st->rbb->w ); -#endif - } - } - - for( int i=0; iaxis_violation ) - continue; - - v3f vxb; - m3x3_mulv( st->rbb->to_world, st->conevxb, vxb ); - - f32 angle = v3_dot( vxb, st->axis_target ); - - if( fabsf(angle) < 0.9999f ){ - v3f axis; - v3_cross( vxb, st->axis_target, axis ); - -#if 1 - angle = acosf(angle) * amt; - v4f correction; - q_axis_angle( correction, axis, angle ); - q_mul( correction, st->rbb->q, st->rbb->q ); - q_normalize( st->rbb->q ); - rb_update_matrices( st->rbb ); -#else - f32 mt = 1.0f/(st->rba->inv_mass+st->rbb->inv_mass), - wa = mt * acosf(angle) * (k_phys_baumgarte/k_rb_delta); - //v3_muladds( st->rba->w, axis, wa*-0.5f, st->rba->w ); - v3_muladds( st->rbb->w, axis, wa* st->rbb->inv_mass, st->rbb->w ); -#endif - } - } -} +void rb_correct_position_constraints( rb_constr_pos *buf, int len, f32 amt ); +void rb_correct_swingtwist_constraints( rb_constr_swingtwist *buf, + int len, float amt ); diff --git a/vg_rigidbody_view.c b/vg_rigidbody_view.c new file mode 100644 index 0000000..b14df65 --- /dev/null +++ b/vg_rigidbody_view.c @@ -0,0 +1,328 @@ +#pragma once +#include "vg_platform.h" +#include "vg_rigidbody.h" +#include "vg_shader.h" +#include "vg_engine.h" +#include "vg_async.h" + +static struct vg_shader _shader_rigidbody = +{ + .name = "[vg] rigidbody", + .link = NULL, + .vs = { + .orig_file = NULL, + .static_src = + + "uniform mat4 uPv;" + "uniform mat4x3 uMdl;" + "uniform mat4x3 uMdl1;" + "layout (location=0) in vec4 a_co;" + "layout (location=1) in vec3 a_norm;" + "out vec3 aNorm;" + "out vec3 aCo;" + "" + "void main()" + "{" + "vec3 world_pos0 = uMdl * vec4( a_co.xyz, 1.0 );" + "vec3 world_pos1 = uMdl1 * vec4( a_co.xyz, 1.0 );" + "vec3 co = mix( world_pos0, world_pos1, a_co.w );" + "vec4 vert_pos = uPv * vec4( co, 1.0 );" + + "gl_Position = vert_pos;" + "vec3 l = vec3(length(uMdl[0]),length(uMdl[1]),length(uMdl[2]));" + "aNorm = (mat3(uMdl) * a_norm)/l;" + "aCo = a_co.xyz*l;" + "}" + }, + .fs = { + .orig_file = NULL, + .static_src = + + "out vec4 FragColor;" + "uniform vec4 uColour;" + "" + "in vec3 aNorm;" + "in vec3 aCo;" + // The MIT License + // Copyright © 2017 Inigo Quilez + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the "Software"), + // to deal in the Software without restriction, including without limitation + // the rights to use, copy, modify, merge, publish, distribute, sublicense, + // and/or sell copies of the Software, and to permit persons to whom the + // Software is furnished to do so, subject to the following conditions: + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. THE SOFTWARE IS + // PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + // DEALINGS IN THE SOFTWARE. + + // Info: https://iquilezles.org/articles/filterableprocedurals + // + // More filtered patterns: https://www.shadertoy.com/playlist/l3KXR1 + + "vec3 tri( in vec3 x ){" + "return 1.0-abs(2.0*fract(x/2.0)-1.0);" + "}" + + "float checkersTextureGrad( in vec3 p, in vec3 ddx, in vec3 ddy ){" + "vec3 w = max(abs(ddx), abs(ddy)) + 0.0001;" // filter kernel + "vec3 i = (tri(p+0.5*w)-tri(p-0.5*w))/w;" // analytical integral + // (box filter) + "return 0.5 - 0.5*i.x*i.y*i.z;" // xor pattern + "}" + "" + "void main()" + "{" + "vec3 uvw = aCo;" + "vec3 ddx_uvw = dFdx( uvw );" + "vec3 ddy_uvw = dFdy( uvw );" + "float diffuse = checkersTextureGrad( uvw, ddx_uvw, ddy_uvw )*0.5+0.4;" + "float light = dot( vec3(0.8017,0.5345,-0.2672), aNorm )*0.5 + 0.5;" + "FragColor = light * diffuse * uColour;" + "}" + } +}; + +#pragma pack(push,1) +struct rb_view_vert { + v4f co; + v3f n; +}; +#pragma pack(pop) + +typedef struct rb_view_vert rb_view_vert; + +struct { + GLuint vao, vbo, ebo; + u32 sphere_start, sphere_count, + box_start, box_count; +} +static vg_rb_view; + +struct vg_rb_mesh_init { + u32 verts_size, tris_size; + rb_view_vert *verts; + u16 *tris; +}; + +static void async_vg_rb_view_init( void *payload, u32 payload_size ) +{ + struct vg_rb_mesh_init *inf = payload; + + glGenVertexArrays( 1, &vg_rb_view.vao ); + glGenBuffers( 1, &vg_rb_view.vbo ); + glGenBuffers( 1, &vg_rb_view.ebo ); + glBindVertexArray( vg_rb_view.vao ); + + + glBindBuffer( GL_ARRAY_BUFFER, vg_rb_view.vbo ); + glBufferData( GL_ARRAY_BUFFER, inf->verts_size, inf->verts, GL_STATIC_DRAW ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_rb_view.ebo ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, + inf->tris_size, inf->tris, GL_STATIC_DRAW ); + + /* 0: coordinates */ + size_t stride = sizeof(rb_view_vert); + glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, stride, (void*)0 ); + glEnableVertexAttribArray( 0 ); + + /* 1: normal */ + glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, + stride, (void *)offsetof(rb_view_vert, n) ); + glEnableVertexAttribArray( 1 ); + + VG_CHECK_GL_ERR(); +} + +void vg_rb_view_init(void) +{ + vg_shader_register( &_shader_rigidbody ); + + u32 H = 20, + V = 16, + verts_count = 0, + tris_count = 0; + + /* box */ + verts_count += 4*6; + tris_count += 2*6; + vg_rb_view.box_count = 2*6; + vg_rb_view.box_start = 0; + + /* sphere */ + verts_count += H*(V-2) + 2; + tris_count += H*2 + (V-2)*(H*2); + vg_rb_view.sphere_count = H*2 + (V-2)*(H*2); + + u32 hdr_size = vg_align8( sizeof(struct vg_rb_mesh_init) ), + vert_size = vg_align8( verts_count * sizeof(rb_view_vert) ), + tris_size = vg_align8( tris_count * 3 * sizeof(u16) ); + + vg_async_item *call = vg_async_alloc( hdr_size + vert_size + tris_size ); + + struct vg_rb_mesh_init *inf = call->payload; + rb_view_vert *verts = ((void *)inf) + hdr_size; + u16 *tris = ((void *)inf) + hdr_size + vert_size; + + inf->verts = verts; + inf->tris = tris; + inf->verts_size = vert_size; + inf->tris_size = tris_size; + + u32 tri_index = 0, + vert_index = 0; + + /* box + * ----------------------------------------------------------- */ + for( u32 i=0; i<6; i ++ ){ + v3f n = {i%3==0,i%3==1,i%3==2}; + if( i >= 3 ) v3_negate( n, n ); + v3f v0, v1; + v3_tangent_basis( n, v0, v1 ); + + rb_view_vert *vs = &verts[vert_index]; + vert_index += 4; + + for( u32 j=0; j<4; j ++ ){ + v3_copy( n, vs[j].n ); + v3_muladds( n, v0, j&0x1?1.0f:-1.0f, vs[j].co ); + v3_muladds( vs[j].co, v1, j&0x2?1.0f:-1.0f, vs[j].co ); + vs[j].co[3] = 0.0f; + } + + tris[tri_index*3+0] = i*4+0; + tris[tri_index*3+1] = i*4+1; + tris[tri_index*3+2] = i*4+3; + tris[tri_index*3+3] = i*4+0; + tris[tri_index*3+4] = i*4+3; + tris[tri_index*3+5] = i*4+2; + tri_index += 2; + } + + /* sphere / capsule + * ----------------------------------------------------------- */ + u32 base = vert_index; + vg_rb_view.sphere_start = tri_index; + v4_copy( (v4f){0,-1,0,0}, verts[vert_index].co ); + v3_copy( (v3f){0,-1,0}, verts[vert_index ++].n ); + + for( u32 x=0; x=(V/2) }; + v4_copy( co, verts[vert_index].co ); + v4_copy( co, verts[vert_index ++].n ); + + if( y < V-2 ){ + tris[tri_index*3+0] = base+1 + ybase*H + x; + tris[tri_index*3+1] = base+1 + (ybase+1)*H + ((x+1)%H); + tris[tri_index*3+2] = base+1 + ybase*H + ((x+1)%H); + tris[tri_index*3+3] = base+1 + ybase*H + x; + tris[tri_index*3+4] = base+1 + (ybase+1)*H + x; + tris[tri_index*3+5] = base+1 + (ybase+1)*H + ((x+1)%H); + tri_index += 2; + } + } + } + + v4_copy( (v4f){0, 1,0,1}, verts[vert_index].co ); + v3_copy( (v3f){0, 1,0}, verts[vert_index ++].n ); + + for( u32 x=0; xverts_size, inf->verts, GL_STATIC_DRAW ); - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_rb_view.ebo ); - glBufferData( GL_ELEMENT_ARRAY_BUFFER, - inf->tris_size, inf->tris, GL_STATIC_DRAW ); - - /* 0: coordinates */ - size_t stride = sizeof(rb_view_vert); - glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, stride, (void*)0 ); - glEnableVertexAttribArray( 0 ); - - /* 1: normal */ - glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, - stride, (void *)offsetof(rb_view_vert, n) ); - glEnableVertexAttribArray( 1 ); - - VG_CHECK_GL_ERR(); -} - -static void vg_rb_view_init(void){ - vg_shader_register( &_shader_rigidbody ); - - u32 H = 20, - V = 16, - verts_count = 0, - tris_count = 0; - - /* box */ - verts_count += 4*6; - tris_count += 2*6; - vg_rb_view.box_count = 2*6; - vg_rb_view.box_start = 0; - - /* sphere */ - verts_count += H*(V-2) + 2; - tris_count += H*2 + (V-2)*(H*2); - vg_rb_view.sphere_count = H*2 + (V-2)*(H*2); - - u32 hdr_size = vg_align8( sizeof(struct vg_rb_mesh_init) ), - vert_size = vg_align8( verts_count * sizeof(rb_view_vert) ), - tris_size = vg_align8( tris_count * 3 * sizeof(u16) ); - - vg_async_item *call = vg_async_alloc( hdr_size + vert_size + tris_size ); - - struct vg_rb_mesh_init *inf = call->payload; - rb_view_vert *verts = ((void *)inf) + hdr_size; - u16 *tris = ((void *)inf) + hdr_size + vert_size; - - inf->verts = verts; - inf->tris = tris; - inf->verts_size = vert_size; - inf->tris_size = tris_size; - - u32 tri_index = 0, - vert_index = 0; - - /* box - * ----------------------------------------------------------- */ - for( u32 i=0; i<6; i ++ ){ - v3f n = {i%3==0,i%3==1,i%3==2}; - if( i >= 3 ) v3_negate( n, n ); - v3f v0, v1; - v3_tangent_basis( n, v0, v1 ); - - rb_view_vert *vs = &verts[vert_index]; - vert_index += 4; - - for( u32 j=0; j<4; j ++ ){ - v3_copy( n, vs[j].n ); - v3_muladds( n, v0, j&0x1?1.0f:-1.0f, vs[j].co ); - v3_muladds( vs[j].co, v1, j&0x2?1.0f:-1.0f, vs[j].co ); - vs[j].co[3] = 0.0f; - } - - tris[tri_index*3+0] = i*4+0; - tris[tri_index*3+1] = i*4+1; - tris[tri_index*3+2] = i*4+3; - tris[tri_index*3+3] = i*4+0; - tris[tri_index*3+4] = i*4+3; - tris[tri_index*3+5] = i*4+2; - tri_index += 2; - } - - /* sphere / capsule - * ----------------------------------------------------------- */ - u32 base = vert_index; - vg_rb_view.sphere_start = tri_index; - v4_copy( (v4f){0,-1,0,0}, verts[vert_index].co ); - v3_copy( (v3f){0,-1,0}, verts[vert_index ++].n ); - - for( u32 x=0; x=(V/2) }; - v4_copy( co, verts[vert_index].co ); - v4_copy( co, verts[vert_index ++].n ); - - if( y < V-2 ){ - tris[tri_index*3+0] = base+1 + ybase*H + x; - tris[tri_index*3+1] = base+1 + (ybase+1)*H + ((x+1)%H); - tris[tri_index*3+2] = base+1 + ybase*H + ((x+1)%H); - tris[tri_index*3+3] = base+1 + ybase*H + x; - tris[tri_index*3+4] = base+1 + (ybase+1)*H + x; - tris[tri_index*3+5] = base+1 + (ybase+1)*H + ((x+1)%H); - tri_index += 2; - } - } - } - - v4_copy( (v4f){0, 1,0,1}, verts[vert_index].co ); - v3_copy( (v3f){0, 1,0}, verts[vert_index ++].n ); - - for( u32 x=0; x= '0') && (buf[i] <= '9')) || (buf[i] == '\0') ) - buf[j ++] = buf[i]; - } -} - -struct ui_textbox_callbacks static vg_settings_ui_int_callbacks = { - .change = vg_settings_ui_int -}; - -static bool vg_settings_ranged_i32_valid( struct vg_setting_ranged_i32 *prop ){ - if( prop->new_value < prop->min ) return 0; - if( prop->new_value > prop->max ) return 0; - return 1; -} - -static bool vg_settings_ranged_i32_diff( struct vg_setting_ranged_i32 *prop ){ - if( prop->new_value != *prop->actual_value ) return 1; - else return 0; -} - -static bool vg_settings_ui_ranged_i32( struct vg_setting_ranged_i32 *prop, - ui_rect rect ){ - ui_rect orig; - rect_copy( rect, orig ); - - ui_textbox( rect, prop->label, prop->buf, sizeof(prop->buf), - 1, 0, &vg_settings_ui_int_callbacks ); - prop->new_value = atoi( prop->buf ); - - if( vg_settings_ranged_i32_diff( prop ) ) - vg_settings_ui_draw_diff( orig ); - - bool valid = vg_settings_ranged_i32_valid( prop ); - if( !valid ){ - ui_rect _null, line; - ui_split( orig, k_ui_axis_h, -1, 0, _null, line ); - line[1] += 3; - - ui_fill( line, ui_colour( k_ui_red ) ); - } - - return valid; -} - -static void ui_settings_ranged_i32_init( struct vg_setting_ranged_i32 *prop ){ - vg_str tmp; - vg_strnull( &tmp, prop->buf, sizeof(prop->buf) ); - vg_strcati32( &tmp, *prop->actual_value ); - prop->new_value = *prop->actual_value; -} - -/* enum settings - * ------------------------------------------------------------------------- */ - -static bool vg_settings_enum_diff( struct vg_setting_enum *prop ){ - if( prop->new_value != *prop->actual_value ) return 1; - else return 0; -} - -static bool vg_settings_enum( struct vg_setting_enum *prop, ui_rect rect ){ - ui_rect orig; - rect_copy( rect, orig ); - - ui_enum( rect, prop->label, - prop->options, prop->option_count, &prop->new_value ); - - if( vg_settings_enum_diff( prop ) ) - vg_settings_ui_draw_diff( orig ); - - return 1; -} - -static void ui_settings_enum_init( struct vg_setting_enum *prop ){ - prop->new_value = *prop->actual_value; -} - -/* .. */ - -static void vg_settings_ui_header( ui_rect inout_panel, const char *name ){ - ui_rect rect; - ui_standard_widget( inout_panel, rect, 2 ); - ui_text( rect, name, 1, k_ui_align_middle_center, ui_colour(k_ui_fg+3) ); -} - - -static bool vg_settings_apply_button( ui_rect inout_panel, bool validated ){ - ui_rect last_row; - ui_px height = (vg_ui.font->glyph_height + 18) * k_ui_scale; - ui_split( inout_panel, k_ui_axis_h, -height, k_ui_padding, - inout_panel, last_row ); - - const char *string = "Apply"; - if( validated ){ - if( ui_button( last_row, string ) == 1 ) - return 1; - } - else{ - ui_rect rect; - ui_standard_widget( last_row, rect, 1 ); - ui_fill( rect, ui_colour( k_ui_bg+1 ) ); - ui_outline( rect, -1, ui_colour( k_ui_red ), 0 ); - - ui_rect t = { 0,0, ui_text_line_width( string ), 14 }; - ui_rect_center( rect, t ); - ui_text( t, string, 1, k_ui_align_left, ui_colour(k_ui_fg+3) ); - } - - return 0; -} - -static void vg_settings_video_apply(void){ - if( vg_settings_enum_diff( &vg_settings.screenmode ) ){ - vg.screen_mode = vg_settings.screenmode.new_value; - - if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ){ - SDL_DisplayMode video_mode; - if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ){ - vg_error("SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError()); - } - else { - //vg.display_refresh_rate = video_mode.refresh_rate; - vg.window_x = video_mode.w; - vg.window_y = video_mode.h; - } - SDL_SetWindowSize( vg.window, vg.window_x, vg.window_y ); - } - - if( vg.screen_mode == 0 ) - SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN_DESKTOP ); - if( vg.screen_mode == 1 ) - SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN ); - if( vg.screen_mode == 2 ){ - SDL_SetWindowFullscreen( vg.window, 0 ); - SDL_SetWindowSize( vg.window, 1280, 720 ); - SDL_SetWindowPosition( vg.window, 16, 16 ); - SDL_SetWindowMinimumSize( vg.window, 1280, 720 ); - SDL_SetWindowMaximumSize( vg.window, 4096, 4096 ); - } - } - - vg.fps_limit = vg_settings.fps_limit.new_value; - vg.quality_profile = vg_settings.quality.new_value; - vg.vsync = vg_settings.vsync.new_value; -} - -static void aaaaaaaaaaaaaaaaa( ui_rect r ); -static void vg_settings_video_gui( ui_rect panel ){ - bool validated = 1; - ui_rect rq; - ui_standard_widget( panel, rq, 1 ); - vg_settings_enum( &vg_settings.quality, rq ); - - /* FIXME */ -#if 0 - if( vg.vsync_feature == k_vsync_feature_error ){ - ui_info( panel, "There was an error activating vsync feature." ); - } -#endif - - /* frame timing */ - vg_settings_ui_header( panel, "Frame Timing" ); - ui_rect duo, d0,d1; - ui_standard_widget( panel, duo, 1 ); - ui_split_ratio( duo, k_ui_axis_v, 0.5f, 16, d0, d1 ); - - vg_settings_enum( &vg_settings.vsync, d0 ); - validated &= vg_settings_ui_ranged_i32( &vg_settings.fps_limit, d1 ); - - ui_standard_widget( panel, duo, 10 ); - aaaaaaaaaaaaaaaaa( duo ); - - /* window spec */ - vg_settings_ui_header( panel, "Window Specification" ); - - ui_standard_widget( panel, duo, 1 ); - vg_settings_enum( &vg_settings.screenmode, duo ); - - if( vg_settings_apply_button( panel, validated ) ) - vg_settings_video_apply(); -} - -static void vg_settings_audio_apply(void){ - if( vg_settings_enum_diff( &vg_settings.audio_devices ) ){ - if( vg_audio.sdl_output_device ){ - vg_info( "Closing audio device %d\n", vg_audio.sdl_output_device ); - SDL_CloseAudioDevice( vg_audio.sdl_output_device ); - } - - vg_strfree( &vg_audio.device_choice ); - - if( vg_settings.audio_devices.new_value == -1 ){ } - else if( vg_settings.audio_devices.new_value == -2 ){ - vg_fatal_error( "Programming error\n" ); - } - else { - struct ui_enum_opt *selected = NULL, *oi; - - for( int i=0; ivalue == vg_settings.audio_devices.new_value ){ - selected = oi; - break; - } - } - - vg_strnull( &vg_audio.device_choice, NULL, -1 ); - vg_strcat( &vg_audio.device_choice, oi->alias ); - } - - vg_audio_device_init(); - *vg_settings.audio_devices.actual_value = - vg_settings.audio_devices.new_value; - } - - audio_lock(); - if( vg_settings_enum_diff( &vg_settings.dsp ) ){ - *vg_settings.dsp.actual_value = - vg_settings.dsp.new_value; - } - - audio_unlock(); -} - -static void vg_settings_audio_gui( ui_rect panel ){ - ui_rect rq; - ui_standard_widget( panel, rq, 1 ); - vg_settings_enum( &vg_settings.audio_devices, rq ); - - ui_standard_widget( panel, rq, 1 ); - vg_settings_enum( &vg_settings.dsp, rq ); - - if( vg_settings_apply_button( panel, 1 ) ) - vg_settings_audio_apply(); -} - -static void vg_settings_open(void){ - vg.settings_open = 1; - - ui_settings_ranged_i32_init( &vg_settings.fps_limit ); - ui_settings_enum_init( &vg_settings.vsync ); - ui_settings_enum_init( &vg_settings.quality ); - ui_settings_enum_init( &vg_settings.screenmode ); - - /* Create audio options */ - int count = SDL_GetNumAudioDevices( 0 ); - - struct ui_enum_opt *options = malloc( sizeof(struct ui_enum_opt)*(count+1) ); - vg_settings.audio_devices.options = options; - vg_settings.audio_devices.option_count = count+1; - - struct ui_enum_opt *o0 = &options[0]; - o0->alias = "OS Default"; - o0->value = -1; - - for( int i=0; ialias = malloc( len+1 ); - memcpy( (void *)oi->alias, device_name, len+1 ); - oi->value = i; - } - - if( vg_audio.device_choice.buffer ){ - vg_settings.temp_audio_choice = -2; - - for( int i=0; ialias, vg_audio.device_choice.buffer ) ){ - vg_settings.temp_audio_choice = oi->value; - break; - } - } - } - else { - vg_settings.temp_audio_choice = -1; - } - - ui_settings_enum_init( &vg_settings.audio_devices ); - ui_settings_enum_init( &vg_settings.dsp ); - -#ifdef VG_GAME_SETTINGS - vg_game_settings_init(); -#endif -} - -static void vg_settings_close(void){ - vg.settings_open = 0; - - struct ui_enum_opt *options = vg_settings.audio_devices.options; - for( int i=1; i < vg_settings.audio_devices.option_count; i ++ ) - free( (void *)options[i].alias ); - free( vg_settings.audio_devices.options ); -} - -static void vg_settings_gui(void){ - ui_rect null; - ui_rect screen = { 0, 0, vg.window_x, vg.window_y }; - ui_rect window = { 0, 0, 1000, 700 }; - ui_rect_center( screen, window ); - vg_ui.wants_mouse = 1; - - ui_fill( window, ui_colour( k_ui_bg+1 ) ); - ui_outline( window, 1, ui_colour( k_ui_bg+7 ), 0 ); - - ui_rect title, panel; - ui_split( window, k_ui_axis_h, 28, 0, title, panel ); - ui_fill( title, ui_colour( k_ui_bg+7 ) ); - ui_text( title, "Settings", 1, k_ui_align_middle_center, - ui_colourcont(k_ui_bg+7) ); - - ui_rect quit_button; - ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button ); - - if( ui_button_text( quit_button, "X", 1 ) == 1 ){ - vg_settings_close(); - return; - } - - ui_rect_pad( panel, (ui_px[2]){ 8, 8 } ); - - const char *opts[] = { "video", "audio", -#ifdef VG_GAME_SETTINGS - "game" -#endif - }; - - static i32 page = 0; - ui_tabs( panel, panel, opts, vg_list_size(opts), &page ); - - if( page == 0 ){ - vg_settings_video_gui( panel ); - } - else if( page == 1 ) - vg_settings_audio_gui( panel ); - -#ifdef VG_GAME_SETTINGS - else if( page == 2 ) - vg_game_settings_gui( panel ); -#endif -} - -static int cmd_vg_settings_toggle( int argc, const char *argv[] ){ - vg_settings_open(); - return 0; -} - -#endif /* VG_SETTINGS_MENU_H */ diff --git a/vg_shader.c b/vg_shader.c new file mode 100644 index 0000000..4b41c2e --- /dev/null +++ b/vg_shader.c @@ -0,0 +1,187 @@ +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ + +#pragma once +#include "vg_platform.h" +#include "vg_shader.h" +#include "vg_engine.h" + +#define STB_INCLUDE_IMPLEMENTATION +#define STB_INCLUDE_LINE_GLSL +#define STB_MALLOC vg_alloc +#define STB_FREE vg_free +#define STB_REALLOC vg_realloc +#include "submodules/stb/stb_include.h" + +const char *vg_shader_gl_ver = "#version 330 core\n"; + +struct vg_shaders vg_shaders; + +static GLuint vg_shader_subshader( const char *src, GLint gliShaderType ) +{ + GLint shader = glCreateShader( gliShaderType ); + + if( shader == GL_NONE ) + { + vg_error( "Could not 'glCreateShader()'\n" ); + return 0; + } + + glShaderSource( shader, 2, (const char*[2]){ vg_shader_gl_ver, src }, NULL ); + glCompileShader( shader ); + + GLint status; + glGetShaderiv( shader, GL_COMPILE_STATUS, &status ); + + if( status != GL_TRUE ) + { + GLchar info[1024]; + GLsizei len; + + glGetShaderInfoLog( shader, sizeof(info), &len, info ); + vg_error( "Error info:\n%s\n", info ); + return 0; + } + + return shader; +} + +int vg_shader_compile( struct vg_shader *shader ) +{ + GLuint program, vert, frag; + + /* If we are compiling this again, we obviously need to try to take the src + * from the disk instead. + * + * Only do this if we have filenames set on the shader, so engine shaders + * dont have to do it (text.. etc). + */ + + int use_source_files = 0; + if( shader->compiled ){ + if( shader->vs.orig_file && shader->fs.orig_file ){ + use_source_files = 1; + } + else { + vg_warn( "No source files for shader '%s'\n", shader->name ); + return 1; + } + } + + vg_info( "Compile shader '%s'\n", shader->name ); + + if( use_source_files ){ + char error[260]; + char path[260]; + + strcpy( path, "../../" ); + strcat( path, shader->vs.orig_file ); + char *vertex_src = stb_include_file( path, "", "../../shaders", error ); + + strcpy( path, "../../" ); + strcat( path, shader->fs.orig_file ); + char *fragment_src = stb_include_file( path, "", "../../shaders", error ); + + if( !vertex_src || !fragment_src ){ + const char *errstr = "Could not find shader source files (%s)\n"; + if( shader->compiled ){ + vg_warn( errstr, shader->vs.orig_file ); + free( vertex_src ); + free( fragment_src ); + return 1; + } + else{ + vg_error( errstr, shader->vs.orig_file ); + free( vertex_src ); + free( fragment_src ); + return 0; + } + } + + vert = vg_shader_subshader( vertex_src, GL_VERTEX_SHADER ); + frag = vg_shader_subshader( fragment_src, GL_FRAGMENT_SHADER ); + + free( vertex_src ); + free( fragment_src ); + } + else{ + vert = vg_shader_subshader( shader->vs.static_src, GL_VERTEX_SHADER ); + frag = vg_shader_subshader( shader->fs.static_src, GL_FRAGMENT_SHADER ); + } + + if( !vert || !frag ) + return 0; + + program = glCreateProgram(); + + glAttachShader( program, vert ); + glAttachShader( program, frag ); + glLinkProgram( program ); + + glDeleteShader( vert ); + glDeleteShader( frag ); + + /* Check for link errors */ + char infoLog[ 512 ]; + int success_link = 1; + + glGetProgramiv( program, GL_LINK_STATUS, &success_link ); + if( !success_link ) + { + glGetProgramInfoLog( program, 512, NULL, infoLog ); + vg_error( "Link failed: %s\n", infoLog ); + glDeleteProgram( program ); + return 0; + } + + if( shader->compiled ) + glDeleteProgram( shader->id ); + + shader->id = program; + shader->compiled = 1; + if( shader->link ) + shader->link(); + return 1; +} + +static void vg_free_shader( struct vg_shader *shader ) +{ + if( shader->compiled ) + { + glDeleteProgram( shader->id ); + shader->compiled = 0; + } +} + +void vg_shaders_compile(void) +{ + vg_info( "Compiling shaders\n" ); + + for( int i=0; icompiled = 0; + shader->id = 0; /* TODO: make this an error shader */ + vg_shaders.shaders[ vg_shaders.count ++ ] = shader; +} diff --git a/vg_shader.h b/vg_shader.h index 211086a..059ed8e 100644 --- a/vg_shader.h +++ b/vg_shader.h @@ -1,24 +1,8 @@ -/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */ - -#ifndef VG_SHADER_H -#define VG_SHADER_H - -#define VG_GAME -#include "vg/vg.h" -#include "vg/vg_platform.h" - -#define STB_INCLUDE_IMPLEMENTATION -#define STB_INCLUDE_LINE_GLSL -#define STB_MALLOC vg_alloc -#define STB_FREE vg_free -#define STB_REALLOC vg_realloc -#include "submodules/stb/stb_include.h" - -const char *vg_shader_gl_ver = "#version 330 core\n"; +#pragma once typedef struct vg_shader vg_shader; -struct +struct vg_shaders { struct vg_shader { @@ -38,176 +22,9 @@ struct * shaders[48]; u32 count; } -static vg_shaders; - -static GLuint vg_shader_subshader( const char *src, GLint gliShaderType ) -{ - GLint shader = glCreateShader( gliShaderType ); - - if( shader == GL_NONE ) - { - vg_error( "Could not 'glCreateShader()'\n" ); - return 0; - } - - glShaderSource( shader, 2, (const char*[2]){ vg_shader_gl_ver, src }, NULL ); - glCompileShader( shader ); - - GLint status; - glGetShaderiv( shader, GL_COMPILE_STATUS, &status ); - - if( status != GL_TRUE ) - { - GLchar info[1024]; - GLsizei len; - - glGetShaderInfoLog( shader, sizeof(info), &len, info ); - vg_error( "Error info:\n%s\n", info ); - return 0; - } - - return shader; -} - -static int vg_shader_compile( struct vg_shader *shader ) -{ - GLuint program, vert, frag; - - /* If we are compiling this again, we obviously need to try to take the src - * from the disk instead. - * - * Only do this if we have filenames set on the shader, so engine shaders - * dont have to do it (text.. etc). - */ - - int use_source_files = 0; - if( shader->compiled ){ - if( shader->vs.orig_file && shader->fs.orig_file ){ - use_source_files = 1; - } - else { - vg_warn( "No source files for shader '%s'\n", shader->name ); - return 1; - } - } - - vg_info( "Compile shader '%s'\n", shader->name ); - - if( use_source_files ){ - char error[260]; - char path[260]; - - strcpy( path, "../../" ); - strcat( path, shader->vs.orig_file ); - char *vertex_src = stb_include_file( path, "", "../../shaders", error ); - - strcpy( path, "../../" ); - strcat( path, shader->fs.orig_file ); - char *fragment_src = stb_include_file( path, "", "../../shaders", error ); - - if( !vertex_src || !fragment_src ){ - const char *errstr = "Could not find shader source files (%s)\n"; - if( shader->compiled ){ - vg_warn( errstr, shader->vs.orig_file ); - free( vertex_src ); - free( fragment_src ); - return 1; - } - else{ - vg_error( errstr, shader->vs.orig_file ); - free( vertex_src ); - free( fragment_src ); - return 0; - } - } - - vert = vg_shader_subshader( vertex_src, GL_VERTEX_SHADER ); - frag = vg_shader_subshader( fragment_src, GL_FRAGMENT_SHADER ); - - free( vertex_src ); - free( fragment_src ); - } - else{ - vert = vg_shader_subshader( shader->vs.static_src, GL_VERTEX_SHADER ); - frag = vg_shader_subshader( shader->fs.static_src, GL_FRAGMENT_SHADER ); - } - - if( !vert || !frag ) - return 0; - - program = glCreateProgram(); - - glAttachShader( program, vert ); - glAttachShader( program, frag ); - glLinkProgram( program ); - - glDeleteShader( vert ); - glDeleteShader( frag ); - - /* Check for link errors */ - char infoLog[ 512 ]; - int success_link = 1; - - glGetProgramiv( program, GL_LINK_STATUS, &success_link ); - if( !success_link ) - { - glGetProgramInfoLog( program, 512, NULL, infoLog ); - vg_error( "Link failed: %s\n", infoLog ); - glDeleteProgram( program ); - return 0; - } - - if( shader->compiled ) - glDeleteProgram( shader->id ); - - shader->id = program; - shader->compiled = 1; - if( shader->link ) - shader->link(); - return 1; -} - -static void vg_free_shader( struct vg_shader *shader ) -{ - if( shader->compiled ) - { - glDeleteProgram( shader->id ); - shader->compiled = 0; - } -} - -static void vg_shaders_compile(void) -{ - vg_info( "Compiling shaders\n" ); - - for( int i=0; icompiled = 0; - shader->id = 0; /* TODO: make this an error shader */ - vg_shaders.shaders[ vg_shaders.count ++ ] = shader; -} +extern vg_shaders; -#endif /* VG_SHADER_H */ +void vg_shaders_compile(void); +int vg_shaders_live_recompile(int argc, const char *argv[]); +void vg_shader_register( struct vg_shader *shader ); +int vg_shader_compile( struct vg_shader *shader ); diff --git a/vg_stdint.h b/vg_stdint.h deleted file mode 100644 index f3e9128..0000000 --- a/vg_stdint.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef VG_STDINT_H -#define VG_STDINT_H - -#include - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; -typedef int8_t i8; -typedef int16_t i16; -typedef int32_t i32; -typedef int64_t i64; -typedef float f32; -typedef double f64; -typedef uint8_t bool; - -#endif /* VG_STDINT_H */ diff --git a/vg_steam.c b/vg_steam.c new file mode 100644 index 0000000..a021cb1 --- /dev/null +++ b/vg_steam.c @@ -0,0 +1,136 @@ +#include "vg_steam.h" +#include "vg_log.h" +#include + +struct vg_steam vg_steam; + +vg_steam_async_call *vg_alloc_async_steam_api_call(void) +{ + if( vg_steam.call_count == vg_list_size(vg_steam.calls) ){ + vg_fatal_error( "Maximum concurrent API calls exceeded (%u)\n", + vg_steam.call_count ); + } + + return &vg_steam.calls[ vg_steam.call_count ++ ]; +} + +void steam_register_callback( u32 id, void (*p_handler)( CallbackMsg_t *msg ) ) +{ + if( vg_steam.callback_handler_count == + vg_list_size(vg_steam.callback_handlers) ) + { + vg_fatal_error( "Too many steam callback handlers registered (%u)\n", + vg_steam.callback_handler_count ); + } + + vg_steam_callback_handler *handler = + &vg_steam.callback_handlers[ vg_steam.callback_handler_count ++ ]; + + handler->p_handler = p_handler; + handler->callback_id = id; +} + +void steamworks_process_api_call( HSteamPipe pipe, CallbackMsg_t *callback ) +{ + SteamAPICallCompleted_t *pCallCompleted = + (SteamAPICallCompleted_t *)callback->m_pubParam; + + steamapi_bool bFailed; + void *call_data = alloca( pCallCompleted->m_cubParam ); + + if( SteamAPI_ManualDispatch_GetAPICallResult( + pipe, + pCallCompleted->m_hAsyncCall, + call_data, + pCallCompleted->m_cubParam, + pCallCompleted->m_iCallback, + &bFailed ) + ) + { + /* + * Dispatch the call result to the registered handler(s) for the + * call identified by pCallCompleted->m_hAsyncCall + */ + + vg_info( "steamworks_event::api_call_completed( %lu )\n", + pCallCompleted->m_hAsyncCall ); + + int j=0; + for( int i=0; im_hAsyncCall ){ + vg_steam.calls[j ++] = vg_steam.calls[i]; + } + else{ + vg_steam_async_call *call = &vg_steam.calls[i]; + call->p_handler( call_data, call->userdata ); + } + } + + if( vg_steam.call_count == j ){ + vg_error( "No tracker was register for API call\n" ); + } + + vg_steam.call_count = j; + } + else + { +#if 0 + typedef enum ESteamAPICallFailure + { + k_ESteamAPICallFailureNone = -1, + k_ESteamAPICallFailureSteamGone = 0, + k_ESteamAPICallFailureNetworkFailure = 1, + k_ESteamAPICallFailureInvalidHandle = 2, + k_ESteamAPICallFailureMismatchedCallback = 3, + } + + ESteamAPICallFailure; + ESteamAPICallFailure fail_why = + SteamAPI_ISteamUtils_GetAPICallFailureReason( + steam_api_classes.utils, pCallCompleted->m_hAsyncCall ); + + vg_error( "steamworks_event: error getting call result on" + "%lu (code %d)\n", + pCallCompleted->m_hAsyncCall, fail_why ); +#endif + } +} + +void steamworks_event_loop( HSteamPipe pipe ) +{ + SteamAPI_ManualDispatch_RunFrame( pipe ); + CallbackMsg_t callback; + + while( SteamAPI_ManualDispatch_GetNextCallback( pipe, &callback ) ){ + vg_low( "steamworks_event::callback( %i )\n", callback.m_iCallback ); + + /* Check for dispatching API call results */ + if( callback.m_iCallback == k_iSteamAPICallCompleted ){ + steamworks_process_api_call( pipe, &callback ); + } + else { + /* + * Look at callback.m_iCallback to see what kind of callback it is, + * and dispatch to appropriate handler(s) + * void *data = callback.m_pubParam; + */ + + for( int i=0; icallback_id == callback.m_iCallback ){ + handler->p_handler( &callback ); + break; + } + } + } + + SteamAPI_ManualDispatch_FreeLastCallback( pipe ); + } +} + +void steamworks_ensure_txt( const char *appid_str ) +{ + FILE *txt = fopen("steam_appid.txt", "w"); + fputs( appid_str, txt ); + fclose( txt ); +} diff --git a/vg_steam.h b/vg_steam.h index 2ca5280..db0e84d 100644 --- a/vg_steam.h +++ b/vg_steam.h @@ -1,470 +1,14 @@ -#ifndef VG_STEAM_H -#define VG_STEAM_H - -#include "vg.h" -#include "vg_log.h" - -/* - * TODO: Combine interfaces and stuff here instead of having them in client code - */ - -#if defined(__linux__) || defined(__APPLE__) -/* - * The 32-bit version of gcc has the alignment requirement for u64 and double - * set to 4 meaning that even with #pragma pack(8) these types will only be - * four-byte aligned. The 64-bit version of gcc has the alignment requirement - * for these types set to 8 meaning that unless we use #pragma pack(4) our - * structures will get bigger. The 64-bit structure packing has to match the - * 32-bit structure packing for each platform. - */ - #define VALVE_CALLBACK_PACK_SMALL -; - #pragma pack( push, 4 ) -#else - #define VALVE_CALLBACK_PACK_LARGE - #pragma pack( push, 8 ) -#endif - -typedef i32 HSteamPipe; -typedef i32 HSteamUser; - -typedef int E_iCallBack_t; - -typedef u64 u64_steamid; -typedef u64 SteamAPICall_t; - -typedef u32 AppId_t; -const AppId_t k_uAppIdInvalid = 0x0; - -typedef u32 DepotId_t; -const DepotId_t k_uDepotIdInvalid = 0x0; - -typedef u32 RTime32; -typedef u32 AccountID_t; -typedef i8 steamapi_bool; - -enum { k_iSteamUserCallbacks = 100 }; -enum { k_iSteamGameServerCallbacks = 200 }; -enum { k_iSteamFriendsCallbacks = 300 }; -enum { k_iSteamBillingCallbacks = 400 }; -enum { k_iSteamMatchmakingCallbacks = 500 }; -enum { k_iSteamContentServerCallbacks = 600 }; -enum { k_iSteamUtilsCallbacks = 700 }; -enum { k_iClientFriendsCallbacks = 800 }; -enum { k_iClientUserCallbacks = 900 }; -enum { k_iSteamAppsCallbacks = 1000 }; -enum { k_iSteamUserStatsCallbacks = 1100 }; -enum { k_iSteamNetworkingCallbacks = 1200 }; -enum { k_iSteamNetworkingSocketsCallbacks = 1220 }; -enum { k_iSteamNetworkingMessagesCallbacks = 1250 }; -enum { k_iSteamNetworkingUtilsCallbacks = 1280 }; -enum { k_iSteamRemoteStorageCallbacks = 1300 }; -enum { k_iClientDepotBuilderCallbacks = 1400 }; -enum { k_iSteamGameServerItemsCallbacks = 1500 }; -enum { k_iClientUtilsCallbacks = 1600 }; -enum { k_iSteamGameCoordinatorCallbacks = 1700 }; -enum { k_iSteamGameServerStatsCallbacks = 1800 }; -enum { k_iSteam2AsyncCallbacks = 1900 }; -enum { k_iSteamGameStatsCallbacks = 2000 }; -enum { k_iClientHTTPCallbacks = 2100 }; -enum { k_iClientScreenshotsCallbacks = 2200 }; -enum { k_iSteamScreenshotsCallbacks = 2300 }; -enum { k_iClientAudioCallbacks = 2400 }; -enum { k_iClientUnifiedMessagesCallbacks = 2500 }; -enum { k_iSteamStreamLauncherCallbacks = 2600 }; -enum { k_iClientControllerCallbacks = 2700 }; -enum { k_iSteamControllerCallbacks = 2800 }; -enum { k_iClientParentalSettingsCallbacks = 2900 }; -enum { k_iClientDeviceAuthCallbacks = 3000 }; -enum { k_iClientNetworkDeviceManagerCallbacks = 3100 }; -enum { k_iClientMusicCallbacks = 3200 }; -enum { k_iClientRemoteClientManagerCallbacks = 3300 }; -enum { k_iClientUGCCallbacks = 3400 }; -enum { k_iSteamUGCCallbacks = 3400 }; -enum { k_iSteamStreamClientCallbacks = 3500 }; -enum { k_IClientProductBuilderCallbacks = 3600 }; -enum { k_iClientShortcutsCallbacks = 3700 }; -enum { k_iClientRemoteControlManagerCallbacks = 3800 }; -enum { k_iSteamAppListCallbacks = 3900 }; -enum { k_iSteamMusicCallbacks = 4000 }; -enum { k_iSteamMusicRemoteCallbacks = 4100 }; -enum { k_iClientVRCallbacks = 4200 }; -enum { k_iClientGameNotificationCallbacks = 4300 }; -enum { k_iSteamGameNotificationCallbacks = 4400 }; -enum { k_iSteamHTMLSurfaceCallbacks = 4500 }; -enum { k_iClientVideoCallbacks = 4600 }; -enum { k_iClientInventoryCallbacks = 4700 }; -enum { k_iClientBluetoothManagerCallbacks = 4800 }; -enum { k_iClientSharedConnectionCallbacks = 4900 }; -enum { k_ISteamParentalSettingsCallbacks = 5000 }; -enum { k_iClientShaderCallbacks = 5100 }; -enum { k_iSteamGameSearchCallbacks = 5200 }; -enum { k_iSteamPartiesCallbacks = 5300 }; -enum { k_iClientPartiesCallbacks = 5400 }; -enum { k_iSteamSTARCallbacks = 5500 }; -enum { k_iClientSTARCallbacks = 5600 }; -enum { k_iSteamRemotePlayCallbacks = 5700 }; -enum { k_iClientCompatCallbacks = 5800 }; -enum { k_iSteamChatCallbacks = 5900 }; - -// General result codes -typedef enum EResult -{ - k_EResultNone = 0, // no result - k_EResultOK = 1, // success - k_EResultFail = 2, // generic failure - k_EResultNoConnection = 3, // no/failed network connection -// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed - k_EResultInvalidPassword = 5, // password/ticket is invalid - k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere - k_EResultInvalidProtocolVer = 7, // protocol version is incorrect - k_EResultInvalidParam = 8, // a parameter is incorrect - k_EResultFileNotFound = 9, // file was not found - k_EResultBusy = 10, // called method busy - action not taken - k_EResultInvalidState = 11, // called object was in an invalid state - k_EResultInvalidName = 12, // name is invalid - k_EResultInvalidEmail = 13, // email is invalid - k_EResultDuplicateName = 14, // name is not unique - k_EResultAccessDenied = 15, // access is denied - k_EResultTimeout = 16, // operation timed out - k_EResultBanned = 17, // VAC2 banned - k_EResultAccountNotFound = 18, // account not found - k_EResultInvalidSteamID = 19, // steamID is invalid - k_EResultServiceUnavailable = 20,// The requested service is currently - // unavailable - k_EResultNotLoggedOn = 21, // The user is not logged on - k_EResultPending = 22, // Request is pending (may be in process, or - // waiting on third party) - k_EResultEncryptionFailure = 23, // Encryption or Decryption failed - k_EResultInsufficientPrivilege = 24,// Insufficient privilege - k_EResultLimitExceeded = 25, // Too much of a good thing - k_EResultRevoked = 26, // Access has been revoked (used for revoked - // guest passes) - k_EResultExpired = 27, // License/Guest pass the user is trying to - // access is expired - k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by - // account, cannot be acked again - k_EResultDuplicateRequest = 29, // The request is a duplicate and the action - // has already occurred in the past, ignored - // this time - k_EResultAlreadyOwned = 30, // All the games in this guest pass - // redemption request are already owned by - // the user - k_EResultIPNotFound = 31, // IP address not found - k_EResultPersistFailed = 32, // failed to write change to the data store - k_EResultLockingFailed = 33, // failed to acquire access lock for this - // operation - k_EResultLogonSessionReplaced = 34, - k_EResultConnectFailed = 35, - k_EResultHandshakeFailed = 36, - k_EResultIOFailure = 37, - k_EResultRemoteDisconnect = 38, - k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart - // requested - k_EResultBlocked = 40, // a user didn't allow it - k_EResultIgnored = 41, // target is ignoring sender - k_EResultNoMatch = 42, // nothing matching the request found - k_EResultAccountDisabled = 43, - k_EResultServiceReadOnly = 44, // this service is not accepting content - // changes right now - k_EResultAccountNotFeatured = 45, // account doesn't have value, so this - // feature isn't available - k_EResultAdministratorOK = 46, // allowed to take this action, but only - // because requester is admin - k_EResultContentVersion = 47, // A Version mismatch in content - // transmitted within the Steam protocol. - k_EResultTryAnotherCM = 48, // The current CM can't service the user - // making a request, user should try - // another. - k_EResultPasswordRequiredToKickSession = 49, // You are already logged in - // elsewhere, this cached credential - // login has failed. - k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in - // elsewhere, you must wait - k_EResultSuspended = 51, // Long running operation (content download) - // suspended/paused - k_EResultCancelled = 52, // Operation canceled (typically by user: - // content download) - k_EResultDataCorruption = 53, // Operation canceled because data is ill - // formed or unrecoverable - k_EResultDiskFull = 54, // Operation canceled - not enough disk space. - k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed - k_EResultPasswordUnset = 56, // Password could not be verified as it's - // unset server side - k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) - // is not linked to a Steam account - k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid - k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, - // Facebook...) is already linked to some other account, - // must explicitly request to replace/delete the link first - k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict - // between the local and remote files - k_EResultIllegalPassword = 61, // The requested new password is not legal - k_EResultSameAsPreviousValue = 62,// new value is the same as the old one ( - // secret question and answer ) - k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor - // authentication failure - k_EResultCannotUseOldPassword = 64, // The requested new password is not - // legal - k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code - // invalid - k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd - // factor auth failure - and no mail - // has been sent - k_EResultHardwareNotCapableOfIPT = 67, - k_EResultIPTInitError = 68, - k_EResultParentalControlRestricted = 69,// operation failed due to parental - // control restrictions for current - // user - k_EResultFacebookQueryError = 70, // Facebook query returned an error - k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth - // code expired - k_EResultIPLoginRestrictionFailed = 72, - k_EResultAccountLockedDown = 73, - k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, - k_EResultNoMatchingURL = 75, - k_EResultBadResponse = 76, // parse failure, missing field, etc. - k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action - // until they re-enter their password - k_EResultValueOutOfRange = 78, // the value entered is outside the - // acceptable range - k_EResultUnexpectedError = 79, // something happened that we didn't expect - // to ever happen - k_EResultDisabled = 80, // The requested service has been configured - // to be unavailable - k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG - // server are not valid ! - k_EResultRestrictedDevice = 82, // The device being used is not allowed - // to perform this action - k_EResultRegionLocked = 83, // The action could not be complete - // because it is region restricted - k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try - // again later, different from - // k_EResultLimitExceeded which may be - // permanent - k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to - // login - k_EResultItemDeleted = 86, // The thing we're trying to access has been - // deleted - k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to - // throttle response to possible - // attacker - k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch - k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for - // two-factor didn't match - k_EResultAccountAssociatedToMultiplePartners = 90, // account has been - // associated with multiple partners - k_EResultNotModified = 91, // data not modified - k_EResultNoMobileDevice = 92, // the account does not have a mobile - // device associated with it - k_EResultTimeNotSynced = 93, // the time presented is out of range or - // tolerance - k_EResultSmsCodeFailed = 94, // SMS code failure (no match, none pending, - // etc.) - k_EResultAccountLimitExceeded = 95, // Too many accounts access this resource - k_EResultAccountActivityLimitExceeded = 96,// Too many changes to - // this account - k_EResultPhoneActivityLimitExceeded = 97, // Too many changes to this phone - k_EResultRefundToWallet = 98, // Cannot refund to payment method, must use - // wallet - k_EResultEmailSendFailure = 99, // Cannot send an email - k_EResultNotSettled = 100, // Can't perform operation till payment - // has settled - k_EResultNeedCaptcha = 101,// Needs to provide a valid captcha - k_EResultGSLTDenied = 102, // a game server login token owned by this token's - // owner has been banned - k_EResultGSOwnerDenied = 103, // game server owner is denied for other reason - // (account lock, community ban, vac ban, missing phone) - k_EResultInvalidItemType = 104,// the type of thing we were requested to act - // on is invalid - k_EResultIPBanned = 105,// the ip address has been banned from taking this - // action - k_EResultGSLTExpired = 106,// this token has expired from disuse; can be - // reset for use - k_EResultInsufficientFunds = 107,// user doesn't have enough wallet funds to - // complete the action - k_EResultTooManyPending = 108, // There are too many of this thing pending - // already - k_EResultNoSiteLicensesFound = 109, // No site licenses found - k_EResultWGNetworkSendExceeded = 110,// the WG couldn't send a response - // because we exceeded max network send size - k_EResultAccountNotFriends = 111, // the user is not mutually friends - k_EResultLimitedUserAccount = 112,// the user is limited - k_EResultCantRemoveItem = 113, // item can't be removed - k_EResultAccountDeleted = 114, // account has been deleted - k_EResultExistingUserCancelledLicense = 115, - // A license for this already exists, but cancelled - k_EResultCommunityCooldown = 116, // access is denied because of a - // community cooldown (probably from support profile data resets) - k_EResultNoLauncherSpecified = 117, // No launcher was specified, but a - // launcher was needed to choose correct realm for operation. - k_EResultMustAgreeToSSA = 118,// User must agree to china SSA or global SSA - // before login - k_EResultLauncherMigrated = 119, // The specified launcher type is no longer - // supported; the user should be directed elsewhere - k_EResultSteamRealmMismatch = 120, // The user's realm does not match the - // realm of the requested resource - k_EResultInvalidSignature = 121, // signature check did not match - k_EResultParseFailure = 122, // Failed to parse input - k_EResultNoVerifiedPhone = 123, // account does not have a verified phone - // number -} EResult; - -typedef struct { - - HSteamUser m_hSteamUser; // Specific user to whom this callback applies. - int m_iCallback; - u8 *m_pubParam; // Points to the callback structure - int m_cubParam; // Size of the data pointed to by m_pubParam - -} CallbackMsg_t; - -typedef struct { - - SteamAPICall_t m_hAsyncCall; - int m_iCallback; - u32 m_cubParam; - -} SteamAPICallCompleted_t; -enum { k_iSteamAPICallCompleted = k_iSteamUtilsCallbacks + 3 }; - -// Steam universes. Each universe is a self-contained Steam instance. -typedef enum EUniverse { - k_EUniverseInvalid = 0, - k_EUniversePublic = 1, - k_EUniverseBeta = 2, - k_EUniverseInternal = 3, - k_EUniverseDev = 4, - // k_EUniverseRC = 5, // no such universe anymore - k_EUniverseMax -} EUniverse_t; - -#pragma pack( push, 1 ) -struct SteamIDComponent_t -{ -#ifdef VALVE_BIG_ENDIAN - EUniverse_t m_EUniverse : 8 - unsigned int m_EAccountType : 4; - unsigned int m_unAccountInstance : 20; - u32 m_unAccountID : 32; -#else - u32 m_unAccountID : 32; - unsigned int m_unAccountInstance : 20; - unsigned int m_EAccountType : 4; - EUniverse_t m_EUniverse : 8; -#endif -}; - -typedef struct -{ - // 64 bits total - union - { - struct SteamIDComponent_t m_comp; - u64 m_unAll64Bits; - }; -} -CSteamID; - -typedef struct GameID_t -{ -#ifdef VALVE_BIG_ENDIAN - unsigned int m_nModID : 32; - unsigned int m_nType : 8; - unsigned int m_nAppID : 24; -#else - unsigned int m_nAppID : 24; - unsigned int m_nType : 8; - unsigned int m_nModID : 32; -#endif -} CGameID; -#pragma pack( pop ) -#pragma pack( pop ) - -/* - * Standard login - * ============================================================================= - */ - -int SteamAPI_RestartAppIfNecessary( u32 unOwnAppID ); -int SteamAPI_Init(void); -void SteamAPI_Shutdown(void); - -/* - * Server mode login - * ============================================================================= - */ - -typedef enum EServerMode EServerMode; -enum EServerMode -{ - eServerModeInvalid = 0, - eServerModeNoAuthentication = 1, - eServerModeAuthentication = 2, - eServerModeAuthenticationAndSecure = 3, -}; - -int SteamInternal_GameServer_Init( u32 unIP, u16 usLegacySteamPort, - u16 usGamePort, u16 usQueryPort, - EServerMode eServerMode, - const char *pchVersionString ); - -/* Initialize SteamGameServer client and interface objects, and set server - * properties which may not be changed. - * After calling this function, you should set any additional server parameters, - * and then call ISteamGameServer::LogOnAnonymous() or ISteamGameServer::LogOn() - * - * - unIP will usually be zero. If you are on a machine with multiple IP - * addresses, you can pass a non-zero value here and the relevant sockets will - * be bound to that IP. This can be used to ensure that the IP you desire is - * the one used in the server browser. - * - usGamePort is the port that clients will connect to for gameplay. You will - * usually open up your own socket bound to this port. - * - usQueryPort is the port that will manage server browser related duties and - * info pings from clients. If you pass STEAMGAMESERVER_QUERY_PORT_SHARED for - * usQueryPort, then it will use "GameSocketShare" mode, which means that the - * game is responsible for sending and receiving UDP packets for the master - * server updater. (See ISteamGameServer::HandleIncomingPacket and - * ISteamGameServer::GetNextOutgoingPacket.) - * - The version string should be in the form x.x.x.x, and is used by the master - * server to detect when the server is out of date. (Only servers with the - * latest version will be listed.) - */ -int SteamGameServer_Init( u32 unIP, u16 usGamePort, u16 usQueryPort, - EServerMode eServerMode, - const char *pchVersionString ) -{ - return SteamInternal_GameServer_Init( unIP, 0, usGamePort, usQueryPort, - eServerMode, pchVersionString ); -} - - -void *SteamAPI_SteamGameServer_v014(void); -void *SteamAPI_SteamGameServer(void) -{ - return SteamAPI_SteamGameServer_v014(); -} - -void SteamAPI_ISteamGameServer_LogOnAnonymous( void* self ); - -void SteamGameServer_Shutdown(void); - -int SteamGameServer_BSecure(void); -u64 SteamGameServer_GetSteamID(void); - -/* - * Async callbacks - * ============================================================================= - */ +#pragma once +#include "vg_steam_api.h" typedef struct vg_steam_async_call vg_steam_async_call; typedef struct vg_steam_callback_handler vg_steam_callback_handler; -struct vg_steam{ +struct vg_steam +{ struct vg_steam_async_call{ SteamAPICall_t id; void *userdata; - void (*p_handler)( void *result, void *userdata ); } calls[4]; @@ -477,166 +21,15 @@ struct vg_steam{ callback_handlers[32]; u32 callback_handler_count; } -static vg_steam; +extern vg_steam; -vg_steam_async_call *vg_alloc_async_steam_api_call(void) -{ - if( vg_steam.call_count == vg_list_size(vg_steam.calls) ){ - vg_fatal_error( "Maximum concurrent API calls exceeded (%u)\n", - vg_steam.call_count ); - } - - return &vg_steam.calls[ vg_steam.call_count ++ ]; -} - -/* - * Regular callbacks - * ============================================================================= - */ - -static void steam_register_callback( u32 id, - void (*p_handler)( CallbackMsg_t *msg ) ) -{ - if( vg_steam.callback_handler_count == - vg_list_size(vg_steam.callback_handlers) ) - { - vg_fatal_error( "Too many steam callback handlers registered (%u)\n", - vg_steam.callback_handler_count ); - } - - vg_steam_callback_handler *handler = - &vg_steam.callback_handlers[ vg_steam.callback_handler_count ++ ]; - - handler->p_handler = p_handler; - handler->callback_id = id; -} - -/* - * Event loop - * ============================================================================= - */ -HSteamPipe SteamAPI_GetHSteamPipe(void); -HSteamPipe SteamGameServer_GetHSteamPipe(void); -HSteamUser SteamAPI_GetHSteamUser(void); -void SteamAPI_ManualDispatch_Init(void); -void SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteamPipe ); -steamapi_bool SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, - CallbackMsg_t *pCallbackMsg ); -void SteamAPI_ManualDispatch_FreeLastCallback( HSteamPipe hSteamPipe ); -steamapi_bool SteamAPI_ManualDispatch_GetAPICallResult( HSteamPipe hSteamPipe, - SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, - int iCallbackExpected, steamapi_bool *pbFailed ); - -void SteamAPI_ReleaseCurrentThreadMemory(void); - -static void steamworks_process_api_call( HSteamPipe pipe, - CallbackMsg_t *callback ) -{ - SteamAPICallCompleted_t *pCallCompleted = - (SteamAPICallCompleted_t *)callback->m_pubParam; - - steamapi_bool bFailed; - void *call_data = alloca( pCallCompleted->m_cubParam ); - - if( SteamAPI_ManualDispatch_GetAPICallResult( - pipe, - pCallCompleted->m_hAsyncCall, - call_data, - pCallCompleted->m_cubParam, - pCallCompleted->m_iCallback, - &bFailed ) - ) - { - /* - * Dispatch the call result to the registered handler(s) for the - * call identified by pCallCompleted->m_hAsyncCall - */ - - vg_info( "steamworks_event::api_call_completed( %lu )\n", - pCallCompleted->m_hAsyncCall ); - - int j=0; - for( int i=0; im_hAsyncCall ){ - vg_steam.calls[j ++] = vg_steam.calls[i]; - } - else{ - vg_steam_async_call *call = &vg_steam.calls[i]; - call->p_handler( call_data, call->userdata ); - } - } - - if( vg_steam.call_count == j ){ - vg_error( "No tracker was register for API call\n" ); - } - - vg_steam.call_count = j; - } - else - { -#if 0 - typedef enum ESteamAPICallFailure - { - k_ESteamAPICallFailureNone = -1, - k_ESteamAPICallFailureSteamGone = 0, - k_ESteamAPICallFailureNetworkFailure = 1, - k_ESteamAPICallFailureInvalidHandle = 2, - k_ESteamAPICallFailureMismatchedCallback = 3, - } - - ESteamAPICallFailure; - ESteamAPICallFailure fail_why = - SteamAPI_ISteamUtils_GetAPICallFailureReason( - steam_api_classes.utils, pCallCompleted->m_hAsyncCall ); - - vg_error( "steamworks_event: error getting call result on" - "%lu (code %d)\n", - pCallCompleted->m_hAsyncCall, fail_why ); -#endif - } -} - -static void steamworks_event_loop( HSteamPipe pipe ) -{ - SteamAPI_ManualDispatch_RunFrame( pipe ); - CallbackMsg_t callback; - - while( SteamAPI_ManualDispatch_GetNextCallback( pipe, &callback ) ){ - vg_low( "steamworks_event::callback( %i )\n", callback.m_iCallback ); - - /* Check for dispatching API call results */ - if( callback.m_iCallback == k_iSteamAPICallCompleted ){ - steamworks_process_api_call( pipe, &callback ); - } - else { - /* - * Look at callback.m_iCallback to see what kind of callback it is, - * and dispatch to appropriate handler(s) - * void *data = callback.m_pubParam; - */ - - for( int i=0; icallback_id == callback.m_iCallback ){ - handler->p_handler( &callback ); - break; - } - } - } - - SteamAPI_ManualDispatch_FreeLastCallback( pipe ); - } -} +void steamworks_process_api_call( HSteamPipe pipe, CallbackMsg_t *callback ); +void steamworks_event_loop( HSteamPipe pipe ); +vg_steam_async_call *vg_alloc_async_steam_api_call(void); +void steam_register_callback( u32 id, void (*p_handler)( CallbackMsg_t *msg ) ); /* * This is required to run the server outside of steamcmd environment. * It can be any appid but idealy the one that is actually your game */ -static void steamworks_ensure_txt( const char *appid_str ) -{ - FILE *txt = fopen("steam_appid.txt", "w"); - fputs( appid_str, txt ); - fclose( txt ); -} - -#endif /* VG_STEAM_H */ +void steamworks_ensure_txt( const char *appid_str ); diff --git a/vg_steam_api.h b/vg_steam_api.h new file mode 100644 index 0000000..2680b5b --- /dev/null +++ b/vg_steam_api.h @@ -0,0 +1,456 @@ +#pragma once +#include "vg_platform.h" + +#if defined(__linux__) || defined(__APPLE__) +/* + * The 32-bit version of gcc has the alignment requirement for u64 and double + * set to 4 meaning that even with #pragma pack(8) these types will only be + * four-byte aligned. The 64-bit version of gcc has the alignment requirement + * for these types set to 8 meaning that unless we use #pragma pack(4) our + * structures will get bigger. The 64-bit structure packing has to match the + * 32-bit structure packing for each platform. + */ + #define VALVE_CALLBACK_PACK_SMALL +; + #pragma pack( push, 4 ) +#else + #define VALVE_CALLBACK_PACK_LARGE + #pragma pack( push, 8 ) +#endif + +typedef i32 HSteamPipe; +typedef i32 HSteamUser; + +typedef int E_iCallBack_t; + +typedef u64 u64_steamid; +typedef u64 SteamAPICall_t; + +typedef u32 AppId_t; +enum { k_uAppIdInvalid = 0x0 }; + +typedef u32 DepotId_t; +enum { k_uDepotIdInvalid = 0x0 }; + +typedef u32 RTime32; +typedef u32 AccountID_t; +typedef i8 steamapi_bool; + +enum { k_iSteamUserCallbacks = 100 }; +enum { k_iSteamGameServerCallbacks = 200 }; +enum { k_iSteamFriendsCallbacks = 300 }; +enum { k_iSteamBillingCallbacks = 400 }; +enum { k_iSteamMatchmakingCallbacks = 500 }; +enum { k_iSteamContentServerCallbacks = 600 }; +enum { k_iSteamUtilsCallbacks = 700 }; +enum { k_iClientFriendsCallbacks = 800 }; +enum { k_iClientUserCallbacks = 900 }; +enum { k_iSteamAppsCallbacks = 1000 }; +enum { k_iSteamUserStatsCallbacks = 1100 }; +enum { k_iSteamNetworkingCallbacks = 1200 }; +enum { k_iSteamNetworkingSocketsCallbacks = 1220 }; +enum { k_iSteamNetworkingMessagesCallbacks = 1250 }; +enum { k_iSteamNetworkingUtilsCallbacks = 1280 }; +enum { k_iSteamRemoteStorageCallbacks = 1300 }; +enum { k_iClientDepotBuilderCallbacks = 1400 }; +enum { k_iSteamGameServerItemsCallbacks = 1500 }; +enum { k_iClientUtilsCallbacks = 1600 }; +enum { k_iSteamGameCoordinatorCallbacks = 1700 }; +enum { k_iSteamGameServerStatsCallbacks = 1800 }; +enum { k_iSteam2AsyncCallbacks = 1900 }; +enum { k_iSteamGameStatsCallbacks = 2000 }; +enum { k_iClientHTTPCallbacks = 2100 }; +enum { k_iClientScreenshotsCallbacks = 2200 }; +enum { k_iSteamScreenshotsCallbacks = 2300 }; +enum { k_iClientAudioCallbacks = 2400 }; +enum { k_iClientUnifiedMessagesCallbacks = 2500 }; +enum { k_iSteamStreamLauncherCallbacks = 2600 }; +enum { k_iClientControllerCallbacks = 2700 }; +enum { k_iSteamControllerCallbacks = 2800 }; +enum { k_iClientParentalSettingsCallbacks = 2900 }; +enum { k_iClientDeviceAuthCallbacks = 3000 }; +enum { k_iClientNetworkDeviceManagerCallbacks = 3100 }; +enum { k_iClientMusicCallbacks = 3200 }; +enum { k_iClientRemoteClientManagerCallbacks = 3300 }; +enum { k_iClientUGCCallbacks = 3400 }; +enum { k_iSteamUGCCallbacks = 3400 }; +enum { k_iSteamStreamClientCallbacks = 3500 }; +enum { k_IClientProductBuilderCallbacks = 3600 }; +enum { k_iClientShortcutsCallbacks = 3700 }; +enum { k_iClientRemoteControlManagerCallbacks = 3800 }; +enum { k_iSteamAppListCallbacks = 3900 }; +enum { k_iSteamMusicCallbacks = 4000 }; +enum { k_iSteamMusicRemoteCallbacks = 4100 }; +enum { k_iClientVRCallbacks = 4200 }; +enum { k_iClientGameNotificationCallbacks = 4300 }; +enum { k_iSteamGameNotificationCallbacks = 4400 }; +enum { k_iSteamHTMLSurfaceCallbacks = 4500 }; +enum { k_iClientVideoCallbacks = 4600 }; +enum { k_iClientInventoryCallbacks = 4700 }; +enum { k_iClientBluetoothManagerCallbacks = 4800 }; +enum { k_iClientSharedConnectionCallbacks = 4900 }; +enum { k_ISteamParentalSettingsCallbacks = 5000 }; +enum { k_iClientShaderCallbacks = 5100 }; +enum { k_iSteamGameSearchCallbacks = 5200 }; +enum { k_iSteamPartiesCallbacks = 5300 }; +enum { k_iClientPartiesCallbacks = 5400 }; +enum { k_iSteamSTARCallbacks = 5500 }; +enum { k_iClientSTARCallbacks = 5600 }; +enum { k_iSteamRemotePlayCallbacks = 5700 }; +enum { k_iClientCompatCallbacks = 5800 }; +enum { k_iSteamChatCallbacks = 5900 }; + +// General result codes +typedef enum EResult +{ + k_EResultNone = 0, // no result + k_EResultOK = 1, // success + k_EResultFail = 2, // generic failure + k_EResultNoConnection = 3, // no/failed network connection +// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed + k_EResultInvalidPassword = 5, // password/ticket is invalid + k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere + k_EResultInvalidProtocolVer = 7, // protocol version is incorrect + k_EResultInvalidParam = 8, // a parameter is incorrect + k_EResultFileNotFound = 9, // file was not found + k_EResultBusy = 10, // called method busy - action not taken + k_EResultInvalidState = 11, // called object was in an invalid state + k_EResultInvalidName = 12, // name is invalid + k_EResultInvalidEmail = 13, // email is invalid + k_EResultDuplicateName = 14, // name is not unique + k_EResultAccessDenied = 15, // access is denied + k_EResultTimeout = 16, // operation timed out + k_EResultBanned = 17, // VAC2 banned + k_EResultAccountNotFound = 18, // account not found + k_EResultInvalidSteamID = 19, // steamID is invalid + k_EResultServiceUnavailable = 20,// The requested service is currently + // unavailable + k_EResultNotLoggedOn = 21, // The user is not logged on + k_EResultPending = 22, // Request is pending (may be in process, or + // waiting on third party) + k_EResultEncryptionFailure = 23, // Encryption or Decryption failed + k_EResultInsufficientPrivilege = 24,// Insufficient privilege + k_EResultLimitExceeded = 25, // Too much of a good thing + k_EResultRevoked = 26, // Access has been revoked (used for revoked + // guest passes) + k_EResultExpired = 27, // License/Guest pass the user is trying to + // access is expired + k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by + // account, cannot be acked again + k_EResultDuplicateRequest = 29, // The request is a duplicate and the action + // has already occurred in the past, ignored + // this time + k_EResultAlreadyOwned = 30, // All the games in this guest pass + // redemption request are already owned by + // the user + k_EResultIPNotFound = 31, // IP address not found + k_EResultPersistFailed = 32, // failed to write change to the data store + k_EResultLockingFailed = 33, // failed to acquire access lock for this + // operation + k_EResultLogonSessionReplaced = 34, + k_EResultConnectFailed = 35, + k_EResultHandshakeFailed = 36, + k_EResultIOFailure = 37, + k_EResultRemoteDisconnect = 38, + k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart + // requested + k_EResultBlocked = 40, // a user didn't allow it + k_EResultIgnored = 41, // target is ignoring sender + k_EResultNoMatch = 42, // nothing matching the request found + k_EResultAccountDisabled = 43, + k_EResultServiceReadOnly = 44, // this service is not accepting content + // changes right now + k_EResultAccountNotFeatured = 45, // account doesn't have value, so this + // feature isn't available + k_EResultAdministratorOK = 46, // allowed to take this action, but only + // because requester is admin + k_EResultContentVersion = 47, // A Version mismatch in content + // transmitted within the Steam protocol. + k_EResultTryAnotherCM = 48, // The current CM can't service the user + // making a request, user should try + // another. + k_EResultPasswordRequiredToKickSession = 49, // You are already logged in + // elsewhere, this cached credential + // login has failed. + k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in + // elsewhere, you must wait + k_EResultSuspended = 51, // Long running operation (content download) + // suspended/paused + k_EResultCancelled = 52, // Operation canceled (typically by user: + // content download) + k_EResultDataCorruption = 53, // Operation canceled because data is ill + // formed or unrecoverable + k_EResultDiskFull = 54, // Operation canceled - not enough disk space. + k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed + k_EResultPasswordUnset = 56, // Password could not be verified as it's + // unset server side + k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) + // is not linked to a Steam account + k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid + k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, + // Facebook...) is already linked to some other account, + // must explicitly request to replace/delete the link first + k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict + // between the local and remote files + k_EResultIllegalPassword = 61, // The requested new password is not legal + k_EResultSameAsPreviousValue = 62,// new value is the same as the old one ( + // secret question and answer ) + k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor + // authentication failure + k_EResultCannotUseOldPassword = 64, // The requested new password is not + // legal + k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code + // invalid + k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd + // factor auth failure - and no mail + // has been sent + k_EResultHardwareNotCapableOfIPT = 67, + k_EResultIPTInitError = 68, + k_EResultParentalControlRestricted = 69,// operation failed due to parental + // control restrictions for current + // user + k_EResultFacebookQueryError = 70, // Facebook query returned an error + k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth + // code expired + k_EResultIPLoginRestrictionFailed = 72, + k_EResultAccountLockedDown = 73, + k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, + k_EResultNoMatchingURL = 75, + k_EResultBadResponse = 76, // parse failure, missing field, etc. + k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action + // until they re-enter their password + k_EResultValueOutOfRange = 78, // the value entered is outside the + // acceptable range + k_EResultUnexpectedError = 79, // something happened that we didn't expect + // to ever happen + k_EResultDisabled = 80, // The requested service has been configured + // to be unavailable + k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG + // server are not valid ! + k_EResultRestrictedDevice = 82, // The device being used is not allowed + // to perform this action + k_EResultRegionLocked = 83, // The action could not be complete + // because it is region restricted + k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try + // again later, different from + // k_EResultLimitExceeded which may be + // permanent + k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to + // login + k_EResultItemDeleted = 86, // The thing we're trying to access has been + // deleted + k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to + // throttle response to possible + // attacker + k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch + k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for + // two-factor didn't match + k_EResultAccountAssociatedToMultiplePartners = 90, // account has been + // associated with multiple partners + k_EResultNotModified = 91, // data not modified + k_EResultNoMobileDevice = 92, // the account does not have a mobile + // device associated with it + k_EResultTimeNotSynced = 93, // the time presented is out of range or + // tolerance + k_EResultSmsCodeFailed = 94, // SMS code failure (no match, none pending, + // etc.) + k_EResultAccountLimitExceeded = 95, // Too many accounts access this resource + k_EResultAccountActivityLimitExceeded = 96,// Too many changes to + // this account + k_EResultPhoneActivityLimitExceeded = 97, // Too many changes to this phone + k_EResultRefundToWallet = 98, // Cannot refund to payment method, must use + // wallet + k_EResultEmailSendFailure = 99, // Cannot send an email + k_EResultNotSettled = 100, // Can't perform operation till payment + // has settled + k_EResultNeedCaptcha = 101,// Needs to provide a valid captcha + k_EResultGSLTDenied = 102, // a game server login token owned by this token's + // owner has been banned + k_EResultGSOwnerDenied = 103, // game server owner is denied for other reason + // (account lock, community ban, vac ban, missing phone) + k_EResultInvalidItemType = 104,// the type of thing we were requested to act + // on is invalid + k_EResultIPBanned = 105,// the ip address has been banned from taking this + // action + k_EResultGSLTExpired = 106,// this token has expired from disuse; can be + // reset for use + k_EResultInsufficientFunds = 107,// user doesn't have enough wallet funds to + // complete the action + k_EResultTooManyPending = 108, // There are too many of this thing pending + // already + k_EResultNoSiteLicensesFound = 109, // No site licenses found + k_EResultWGNetworkSendExceeded = 110,// the WG couldn't send a response + // because we exceeded max network send size + k_EResultAccountNotFriends = 111, // the user is not mutually friends + k_EResultLimitedUserAccount = 112,// the user is limited + k_EResultCantRemoveItem = 113, // item can't be removed + k_EResultAccountDeleted = 114, // account has been deleted + k_EResultExistingUserCancelledLicense = 115, + // A license for this already exists, but cancelled + k_EResultCommunityCooldown = 116, // access is denied because of a + // community cooldown (probably from support profile data resets) + k_EResultNoLauncherSpecified = 117, // No launcher was specified, but a + // launcher was needed to choose correct realm for operation. + k_EResultMustAgreeToSSA = 118,// User must agree to china SSA or global SSA + // before login + k_EResultLauncherMigrated = 119, // The specified launcher type is no longer + // supported; the user should be directed elsewhere + k_EResultSteamRealmMismatch = 120, // The user's realm does not match the + // realm of the requested resource + k_EResultInvalidSignature = 121, // signature check did not match + k_EResultParseFailure = 122, // Failed to parse input + k_EResultNoVerifiedPhone = 123, // account does not have a verified phone + // number +} EResult; + +typedef struct { + + HSteamUser m_hSteamUser; // Specific user to whom this callback applies. + int m_iCallback; + u8 *m_pubParam; // Points to the callback structure + int m_cubParam; // Size of the data pointed to by m_pubParam + +} CallbackMsg_t; + +typedef struct { + + SteamAPICall_t m_hAsyncCall; + int m_iCallback; + u32 m_cubParam; + +} SteamAPICallCompleted_t; +enum { k_iSteamAPICallCompleted = k_iSteamUtilsCallbacks + 3 }; + +// Steam universes. Each universe is a self-contained Steam instance. +typedef enum EUniverse { + k_EUniverseInvalid = 0, + k_EUniversePublic = 1, + k_EUniverseBeta = 2, + k_EUniverseInternal = 3, + k_EUniverseDev = 4, + // k_EUniverseRC = 5, // no such universe anymore + k_EUniverseMax +} EUniverse_t; + +#pragma pack( push, 1 ) +struct SteamIDComponent_t +{ +#ifdef VALVE_BIG_ENDIAN + EUniverse_t m_EUniverse : 8 + unsigned int m_EAccountType : 4; + unsigned int m_unAccountInstance : 20; + u32 m_unAccountID : 32; +#else + u32 m_unAccountID : 32; + unsigned int m_unAccountInstance : 20; + unsigned int m_EAccountType : 4; + EUniverse_t m_EUniverse : 8; +#endif +}; + +typedef struct +{ + // 64 bits total + union + { + struct SteamIDComponent_t m_comp; + u64 m_unAll64Bits; + }; +} +CSteamID; + +typedef struct GameID_t +{ +#ifdef VALVE_BIG_ENDIAN + unsigned int m_nModID : 32; + unsigned int m_nType : 8; + unsigned int m_nAppID : 24; +#else + unsigned int m_nAppID : 24; + unsigned int m_nType : 8; + unsigned int m_nModID : 32; +#endif +} CGameID; +#pragma pack( pop ) +#pragma pack( pop ) + +/* + * Standard login + * ============================================================================= + */ + +int SteamAPI_RestartAppIfNecessary( u32 unOwnAppID ); +int SteamAPI_Init(void); +void SteamAPI_Shutdown(void); + +/* + * Server mode login + * ============================================================================= + */ + +typedef enum EServerMode EServerMode; +enum EServerMode +{ + eServerModeInvalid = 0, + eServerModeNoAuthentication = 1, + eServerModeAuthentication = 2, + eServerModeAuthenticationAndSecure = 3, +}; + +int SteamInternal_GameServer_Init( u32 unIP, u16 usLegacySteamPort, + u16 usGamePort, u16 usQueryPort, + EServerMode eServerMode, + const char *pchVersionString ); + +/* Initialize SteamGameServer client and interface objects, and set server + * properties which may not be changed. + * After calling this function, you should set any additional server parameters, + * and then call ISteamGameServer::LogOnAnonymous() or ISteamGameServer::LogOn() + * + * - unIP will usually be zero. If you are on a machine with multiple IP + * addresses, you can pass a non-zero value here and the relevant sockets will + * be bound to that IP. This can be used to ensure that the IP you desire is + * the one used in the server browser. + * - usGamePort is the port that clients will connect to for gameplay. You will + * usually open up your own socket bound to this port. + * - usQueryPort is the port that will manage server browser related duties and + * info pings from clients. If you pass STEAMGAMESERVER_QUERY_PORT_SHARED for + * usQueryPort, then it will use "GameSocketShare" mode, which means that the + * game is responsible for sending and receiving UDP packets for the master + * server updater. (See ISteamGameServer::HandleIncomingPacket and + * ISteamGameServer::GetNextOutgoingPacket.) + * - The version string should be in the form x.x.x.x, and is used by the master + * server to detect when the server is out of date. (Only servers with the + * latest version will be listed.) + */ +static inline int SteamGameServer_Init( + u32 unIP, u16 usGamePort, u16 usQueryPort, + EServerMode eServerMode, + const char *pchVersionString ) +{ + return SteamInternal_GameServer_Init( unIP, 0, usGamePort, usQueryPort, + eServerMode, pchVersionString ); +} + +void *SteamAPI_SteamGameServer_v014(void); +static inline void *SteamAPI_SteamGameServer(void) +{ + return SteamAPI_SteamGameServer_v014(); +} + +void SteamAPI_ISteamGameServer_LogOnAnonymous( void* self ); +void SteamGameServer_Shutdown(void); +int SteamGameServer_BSecure(void); +u64 SteamGameServer_GetSteamID(void); +HSteamPipe SteamAPI_GetHSteamPipe(void); +HSteamPipe SteamGameServer_GetHSteamPipe(void); +HSteamUser SteamAPI_GetHSteamUser(void); +void SteamAPI_ManualDispatch_Init(void); +void SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteamPipe ); +steamapi_bool SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, + CallbackMsg_t *pCallbackMsg ); +void SteamAPI_ManualDispatch_FreeLastCallback( HSteamPipe hSteamPipe ); +steamapi_bool SteamAPI_ManualDispatch_GetAPICallResult( HSteamPipe hSteamPipe, + SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, + int iCallbackExpected, steamapi_bool *pbFailed ); +void SteamAPI_ReleaseCurrentThreadMemory(void); diff --git a/vg_steam_auth.h b/vg_steam_auth.h index fcdc8ae..f0cd8cc 100644 --- a/vg_steam_auth.h +++ b/vg_steam_auth.h @@ -1,13 +1,11 @@ -#ifndef VG_STEAM_AUTH_H -#define VG_STEAM_AUTH_H - +#pragma once #include "vg/vg_platform.h" #include "vg/vg_io.h" +#include "vg_steam.h" typedef u32 HAuthTicket; enum{ k_HAuthTicketInvalid = 0 }; -#include "vg_steam.h" #if defined( VALVE_CALLBACK_PACK_SMALL ) #pragma pack( push, 4 ) #elif defined( VALVE_CALLBACK_PACK_LARGE ) @@ -37,7 +35,7 @@ enum{ k_iGetAuthSessionTicketResponse = k_iSteamUserCallbacks + 63 }; typedef void ISteamUser; ISteamUser *SteamAPI_SteamUser_v021(void); -ISteamUser *SteamAPI_SteamUser(void) +static inline ISteamUser *SteamAPI_SteamUser(void) { return SteamAPI_SteamUser_v021(); } @@ -105,7 +103,7 @@ steamapi_bool SteamEncryptedAppTicket_BIsLicenseBorrowed( steamapi_bool SteamEncryptedAppTicket_BIsLicenseTemporary( u8 *rgubTicketDecrypted, u32 cubTicketDecrypted ); -static u8 vg_char_base16( char c ) +static inline u8 vg_char_base16( char c ) { if( c >= '0' && c <= '9' ) return c-'0'; @@ -115,7 +113,7 @@ static u8 vg_char_base16( char c ) return 0; } -static int vg_load_steam_symetric_key( const char *path, u8 *buf ) +static inline int vg_load_steam_symetric_key( const char *path, u8 *buf ) { vg_linear_clear( vg_mem.scratch ); u32 size; @@ -143,6 +141,3 @@ static int vg_load_steam_symetric_key( const char *path, u8 *buf ) return 0; } } - - -#endif /* VG_STEAM_AUTH_H */ diff --git a/vg_steam_friends.h b/vg_steam_friends.h index 0a7f017..04685a8 100644 --- a/vg_steam_friends.h +++ b/vg_steam_friends.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_FRIENDS_H -#define VG_STEAM_FRIENDS_H - +#pragma once #include "vg_steam.h" #if defined( VALVE_CALLBACK_PACK_SMALL ) @@ -66,7 +64,7 @@ enum { k_iPersonaStateChange = k_iSteamFriendsCallbacks + 4 }; typedef void ISteamFriends; ISteamFriends *SteamAPI_SteamFriends_v017(void); -ISteamFriends *SteamAPI_SteamFriends(void) +static inline ISteamFriends *SteamAPI_SteamFriends(void) { return SteamAPI_SteamFriends_v017(); } @@ -113,5 +111,3 @@ enum EFriendFlags{ steamapi_bool SteamAPI_ISteamFriends_HasFriend( ISteamFriends* self, u64_steamid steamIDFriend, int iFriendFlags ); - -#endif /* VG_STEAM_FRIENDS_H */ diff --git a/vg_steam_http.h b/vg_steam_http.h index 6cf467e..384674e 100644 --- a/vg_steam_http.h +++ b/vg_steam_http.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_HTTP_H -#define VG_STEAM_HTTP_H - +#pragma once #include "vg_steam.h" /* @@ -132,14 +130,14 @@ struct HTTPRequestCompleted_t */ void *SteamAPI_SteamGameServerHTTP_v003(void); -void *SteamAPI_SteamGameServerHTTP(void) +static inline void *SteamAPI_SteamGameServerHTTP(void) { return SteamAPI_SteamGameServerHTTP_v003(); } /* Interfaces */ void *SteamAPI_SteamHTTP_v003(void); -void *SteamAPI_SteamHTTP(void) +static inline void *SteamAPI_SteamHTTP(void) { return SteamAPI_SteamHTTP_v003(); } @@ -158,5 +156,3 @@ steamapi_bool SteamAPI_ISteamHTTP_GetHTTPResponseBodySize( void *self, steamapi_bool SteamAPI_ISteamHTTP_GetHTTPResponseBodyData( void* self, HTTPRequestHandle hRequest, u8 *pBodyDataBuffer, u32 unBufferSize ); - -#endif /* VG_STEAM_HTTP_H */ diff --git a/vg_steam_networking.h b/vg_steam_networking.h index 7d10278..da933f7 100644 --- a/vg_steam_networking.h +++ b/vg_steam_networking.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_NETWORKING_H -#define VG_STEAM_NETWORKING_H - +#pragma once #include "vg_steam.h" #ifdef VALVE_CALLBACK_PACK_SMALL @@ -398,7 +396,7 @@ HSteamNetPollGroup const k_HSteamNetPollGroup_Invalid = 0; ISteamNetworkingSockets *SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012(void); -ISteamNetworkingSockets +static inline ISteamNetworkingSockets *SteamAPI_SteamGameServerNetworkingSockets_SteamAPI(void) { return SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012(); @@ -406,6 +404,7 @@ ISteamNetworkingSockets ISteamNetworkingSockets *SteamAPI_SteamNetworkingSockets_SteamAPI_v012(); +static inline ISteamNetworkingSockets *SteamAPI_SteamNetworkingSockets_SteamAPI() { return SteamAPI_SteamNetworkingSockets_SteamAPI_v012(); @@ -933,7 +932,7 @@ void SteamAPI_SteamNetworkingMessage_t_Release(SteamNetworkingMessage_t* self); * Utility */ -static const char *string_ESteamNetworkingConnectionState( +static inline const char *string_ESteamNetworkingConnectionState( ESteamNetworkingConnectionState s ) { switch(s) @@ -973,7 +972,7 @@ static const char *string_ESteamNetworkingConnectionState( return "Error"; } -static const char *string_ESteamNetworkingAvailability( +static inline const char *string_ESteamNetworkingAvailability( ESteamNetworkingAvailability s ) { switch(s) @@ -1010,5 +1009,3 @@ static const char *string_ESteamNetworkingAvailability( break; } } - -#endif /* VG_STEAM_NETWORKING_H */ diff --git a/vg_steam_remote_storage.h b/vg_steam_remote_storage.h index d724c20..24aaf3b 100644 --- a/vg_steam_remote_storage.h +++ b/vg_steam_remote_storage.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_REMOTE_STORAGE_H -#define VG_STEAM_REMOTE_STORAGE_H - +#pragma once #include "vg_steam.h" const u32 k_unMaxCloudFileChunkSize = 100 * 1024 * 1024; @@ -423,7 +421,8 @@ enum { k_iRemoteStorageFileReadAsyncComplete_t = #pragma pack( pop ) ISteamRemoteStorage *SteamAPI_SteamRemoteStorage_v016(); -ISteamRemoteStorage *SteamAPI_SteamRemoteStorage(){ +static inline ISteamRemoteStorage *SteamAPI_SteamRemoteStorage() +{ return SteamAPI_SteamRemoteStorage_v016(); } @@ -581,5 +580,3 @@ steamapi_bool SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch( ISteamRemoteStorage *self ); steamapi_bool SteamAPI_ISteamRemoteStorage_EndFileWriteBatch( ISteamRemoteStorage *self ); - -#endif /* VG_STEAM_REMOTE_STORAGE_H */ diff --git a/vg_steam_ugc.h b/vg_steam_ugc.h index cb0bfc6..48594b2 100644 --- a/vg_steam_ugc.h +++ b/vg_steam_ugc.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_UGC_H -#define VG_STEAM_UGC_H - +#pragma once #include "vg_steam.h" #include "vg_steam_remote_storage.h" @@ -489,12 +487,12 @@ enum { k_iWorkshopEULAStatus = k_iSteamUGCCallbacks + 20 }; #define STEAMUGC_INTERFACE_VERSION "STEAMUGC_INTERFACE_VERSION016" ISteamUGC *SteamAPI_SteamUGC_v016(); -ISteamUGC *SteamAPI_SteamUGC() +static inline ISteamUGC *SteamAPI_SteamUGC() { return SteamAPI_SteamUGC_v016(); } ISteamUGC *SteamAPI_SteamGameServerUGC_v016(); -ISteamUGC *SteamAPI_SteamGameServerUGC() +static inline ISteamUGC *SteamAPI_SteamGameServerUGC() { return SteamAPI_SteamGameServerUGC_v016(); } @@ -775,5 +773,3 @@ SteamAPICall_t SteamAPI_ISteamUGC_DeleteItem( ISteamUGC* self, steamapi_bool SteamAPI_ISteamUGC_ShowWorkshopEULA( ISteamUGC* self ); SteamAPICall_t SteamAPI_ISteamUGC_GetWorkshopEULAStatus( ISteamUGC* self ); - -#endif /* VG_STEAM_UGC_H */ diff --git a/vg_steam_user_stats.h b/vg_steam_user_stats.h index 25ff5c0..6f5774e 100644 --- a/vg_steam_user_stats.h +++ b/vg_steam_user_stats.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_USER_STATS_H -#define VG_STEAM_USER_STATS_H - +#pragma once #include "vg_steam.h" #if defined( VALVE_CALLBACK_PACK_SMALL ) @@ -23,7 +21,7 @@ enum { k_iUserStatsReceived = k_iSteamUserStatsCallbacks + 1 }; typedef void ISteamUserStats; ISteamUserStats *SteamAPI_SteamUserStats_v012(void); -ISteamUserStats *SteamAPI_SteamUserStats(void) +static inline ISteamUserStats *SteamAPI_SteamUserStats(void) { return SteamAPI_SteamUserStats_v012(); } @@ -41,5 +39,3 @@ steamapi_bool SteamAPI_ISteamUserStats_ClearAchievement( ISteamUserStats *self, const char *pchName ); steamapi_bool SteamAPI_ISteamUserStats_StoreStats( ISteamUserStats* self ); - -#endif /* VG_STEAM_USER_STATS_H */ diff --git a/vg_steam_utils.h b/vg_steam_utils.h index 567a7bd..43f8bba 100644 --- a/vg_steam_utils.h +++ b/vg_steam_utils.h @@ -1,6 +1,4 @@ -#ifndef VG_STEAM_UTILS_H -#define VG_STEAM_UTILS_H - +#pragma once #include "vg_steam.h" #if defined( VALVE_CALLBACK_PACK_SMALL ) @@ -17,7 +15,7 @@ typedef void ISteamUtils; typedef void ISteamInput; ISteamUtils *SteamAPI_SteamUtils_v010(void); -ISteamUtils *SteamAPI_SteamUtils(void) +static inline ISteamUtils *SteamAPI_SteamUtils(void) { return SteamAPI_SteamUtils_v010(); } @@ -27,7 +25,7 @@ steamapi_bool SteamAPI_ISteamUtils_SetWarningMessageHook( ISteamInput *SteamAPI_SteamInput_v006(void); -ISteamInput *SteamAPI_SteamInput(void) +static inline ISteamInput *SteamAPI_SteamInput(void) { return SteamAPI_SteamInput_v006(); } @@ -65,6 +63,3 @@ ESteamInputType SteamAPI_ISteamInput_GetInputTypeForHandle( ISteamInput* self, InputHandle_t inputHandle ); void SteamAPI_ISteamInput_RunFrame( ISteamInput* self, steamapi_bool bReservedValue ); - - -#endif /* VG_STEAM_UTILS_H */ diff --git a/vg_store.h b/vg_store.h deleted file mode 100644 index ee0e91a..0000000 --- a/vg_store.h +++ /dev/null @@ -1,681 +0,0 @@ -#ifndef VG_STORE_H -#define VG_STORE_H - -#include "vg_stdint.h" -#include "vg_io.h" -#include "vg_log.h" - -/* - * Anderson tree implementation with extensions: - * parents are kept track of - * duplicates are allowed - * data is never allocated or destroyed here - * - * TODO: seperate offset,stride,base into 'generic array', seperate pool - */ - -typedef struct aatree aatree; -typedef struct aatree_node aatree_node; -typedef struct aatree_pool_node aatree_pool_node; - -typedef u32 aatree_ptr; -#define AATREE_PTR_NIL 0xffffffff - -struct aatree -{ - u32 offset, stride; /* distance between elements */ - void *base; - - int (*p_cmp)( void *a, void *b ); -}; - -#pragma pack(push,1) -struct aatree_node -{ - aatree_ptr left, right, parent; - u32 level, count; -}; - -struct aatree_pool_node -{ - aatree_ptr next_free; -}; -#pragma pack(pop) - -/* api - * ===========================================================================*/ - -/* return a pointer to the start of the data referenced by t */ -static void *aatree_get_data( aatree *tree, aatree_ptr t ); - -/* link node x into the tree with root t */ -static aatree_ptr aatree_insert( aatree *tree, aatree_ptr t, aatree_ptr x ); - -/* delete node x from tree, does not free memory */ -static aatree_ptr aatree_del( aatree *tree, aatree_ptr x ); - -/* get pointer to element in tree with index k */ -static aatree_ptr aatree_kth( aatree *tree, aatree_ptr t, u32 k ); - -/* get pointer to the element above x */ -static aatree_ptr aatree_next( aatree *tree, aatree_ptr x ); - -/* get pointer by value, returns NIL if not found. - * - * if duplicates values are present then the result is undefined, but it will be - * AN node with that value, just maybe not the first lexicographically - */ -static aatree_ptr aatree_find( aatree *tree, aatree_ptr t, void *value ); - -/* implementation - * ===========================================================================*/ - -static void *aatree_get_data( aatree *tree, aatree_ptr t ) -{ - return (u8 *)tree->base + tree->stride*t; -} - -static u8 *aatree_node_base( aatree *tree, aatree_ptr t ) -{ - return (u8 *)tree->base + tree->stride*t + tree->offset; -} - -static aatree_pool_node *aatree_get_pool_node( aatree *tree, aatree_ptr t ) -{ - return (aatree_pool_node *)aatree_node_base( tree, t ); -} - -static aatree_node *aatree_get_node( aatree *tree, aatree_ptr t ) -{ - return (aatree_node *)aatree_node_base( tree, t ); -} - -static void aatree_recount( aatree *tree, aatree_ptr n ) -{ - aatree_node *pnode = aatree_get_node( tree, n ); - pnode->count = 1; - - if( pnode->left != AATREE_PTR_NIL ) - pnode->count += aatree_get_node( tree, pnode->left )->count; - - if( pnode->right != AATREE_PTR_NIL ) - pnode->count += aatree_get_node( tree, pnode->right )->count; -} - -/* . . - * | | - * L <- T L -> T - * / \ \ -> / / \ - * A B R A B R - */ -static aatree_ptr aatree_skew( aatree *tree, u32 t ) -{ - if( t == AATREE_PTR_NIL ) return t; - - aatree_node *ptnode = aatree_get_node( tree, t ); - if( ptnode->left == AATREE_PTR_NIL ) return t; - - aatree_node *plnode = aatree_get_node( tree, ptnode->left ); - if( plnode->level == ptnode->level ) - { - aatree_ptr l = ptnode->left; - ptnode->left = plnode->right; - plnode->right = t; - - aatree_recount( tree, t ); - aatree_recount( tree, l ); - - plnode->parent = ptnode->parent; - ptnode->parent = l; - if( ptnode->left != AATREE_PTR_NIL ) - aatree_get_node( tree, ptnode->left )->parent = t; - - return l; - } - - return t; -} - -/* . . - * | | - * T -> R -> X -> R - * / / / \ - * A B T X - * / \ - * A B - */ -static aatree_ptr aatree_split( aatree *tree, aatree_ptr t ) -{ - if( t == AATREE_PTR_NIL ) return t; - - aatree_node *ptnode = aatree_get_node( tree, t ); - if( ptnode->right == AATREE_PTR_NIL ) return t; - - aatree_node *prnode = aatree_get_node( tree, ptnode->right ); - if( prnode->right == AATREE_PTR_NIL ) return t; - - aatree_node *prrnode = aatree_get_node( tree, prnode->right ); - if( ptnode->level == prrnode->level ) - { - aatree_ptr r = ptnode->right; - ptnode->right = prnode->left; - prnode->left = t; - prnode->level ++; - - aatree_recount( tree, t ); - aatree_recount( tree, r ); - - prnode->parent = ptnode->parent; - ptnode->parent = r; - if( ptnode->right != AATREE_PTR_NIL ) - aatree_get_node( tree, ptnode->right )->parent = t; - - return r; - } - - return t; -} - -static aatree_ptr aatree_insert( aatree *tree, aatree_ptr t, aatree_ptr x ) -{ - aatree_node *pxnode = aatree_get_node( tree, x ); - - if( t == AATREE_PTR_NIL ) - { - pxnode->left = AATREE_PTR_NIL; - pxnode->right = AATREE_PTR_NIL; - pxnode->parent = AATREE_PTR_NIL; - pxnode->level = 0; - pxnode->count = 1; - return x; - } - - aatree_node *ptnode = aatree_get_node( tree, t ); - int cmp_result = tree->p_cmp( aatree_get_data( tree, t ), - aatree_get_data( tree, x ) ); - - ptnode->count ++; - - if( cmp_result <= 0 ) - { - ptnode->left = aatree_insert( tree, ptnode->left, x ); - aatree_node *plnode = aatree_get_node( tree, ptnode->left ); - plnode->parent = t; - } - else - { - ptnode->right = aatree_insert( tree, ptnode->right, x ); - aatree_node *prnode = aatree_get_node( tree, ptnode->right ); - prnode->parent = t; - } - - t = aatree_skew( tree, t ); - t = aatree_split( tree, t ); - return t; -} - -static void aatree_link_down( aatree *tree, aatree_ptr p, aatree_ptr *pl, - aatree_ptr l ) -{ - *pl = l; - - if( *pl != AATREE_PTR_NIL ) - aatree_get_node( tree, *pl )->parent = p; -} - -static aatree_ptr aatree_copy_links( aatree *tree, aatree_ptr root, - aatree_ptr src, aatree_ptr dst ) -{ - aatree_node *pdst = aatree_get_node( tree, dst ), - *psrc = aatree_get_node( tree, src ); - - pdst->count = psrc->count; - pdst->level = psrc->level; - pdst->parent = psrc->parent; - - aatree_link_down( tree, dst, &pdst->left, psrc->left ); - aatree_link_down( tree, dst, &pdst->right, psrc->right ); - - if( pdst->parent != AATREE_PTR_NIL ) - { - aatree_node *parent = aatree_get_node( tree, pdst->parent ); - - if( parent->left == src ) - parent->left = dst; - else if( parent->right == src ) - parent->right = dst; - } - else - return dst; - - return root; -} - -static aatree_ptr aatree_del( aatree *tree, aatree_ptr x ) -{ - aatree_ptr it = x, - up[32]; - - int count = 1, dir = 0; - - /* TODO: maybe be a better way to do this, without counting back up */ - for( aatree_node *s = aatree_get_node( tree, x ); - s->parent != AATREE_PTR_NIL; - count ++ ) - s = aatree_get_node( tree, s->parent ); - - int top=0; - while(1) - { - int index = count - (++top); - - up[ index ] = it; - aatree_node *itnode = aatree_get_node( tree, it ); - if( itnode->parent == AATREE_PTR_NIL ) - break; - else - it = itnode->parent; - } - - aatree_ptr _ptrswap_src = AATREE_PTR_NIL, - _ptrswap_dst = AATREE_PTR_NIL; - - aatree_node *pxnode = aatree_get_node( tree, x ); - aatree_ptr root = up[ count-1 ]; - if( pxnode->left == AATREE_PTR_NIL || pxnode->right == AATREE_PTR_NIL ) - { - if( --top != 0 ) - { - aatree_node *pnode = aatree_get_node( tree, up[top-1] ), - *parent = aatree_get_node( tree, pxnode->parent ); - - aatree_ptr next = pxnode->left == AATREE_PTR_NIL? - pxnode->right: - pxnode->left; - - if( parent->left == x ) pnode->left = next; - else pnode->right = next; - - if( next != AATREE_PTR_NIL ) - { - aatree_node *pnext = aatree_get_node( tree, next ); - pnext->parent = up[top-1]; - } - } - else - { - if( pxnode->right != AATREE_PTR_NIL ) root = pxnode->right; - else if( pxnode->left != AATREE_PTR_NIL ) root = pxnode->left; - else return AATREE_PTR_NIL; - - aatree_node *newroot = aatree_get_node( tree, root ); - newroot->parent = AATREE_PTR_NIL; - } - } - else - { - aatree_ptr heir = pxnode->right, - prev = x; - - aatree_node *pheir = aatree_get_node( tree, heir ); - - while( pheir->left != AATREE_PTR_NIL ) - { - up[top++] = prev = heir; - heir = pheir->left; - pheir = aatree_get_node( tree, heir ); - } - - _ptrswap_dst = heir; - _ptrswap_src = x; - - aatree_node *pprev = aatree_get_node( tree, prev ); - - if( prev == x ) - aatree_link_down( tree, prev, &pprev->right, pheir->right ); - else - aatree_link_down( tree, prev, &pprev->left, pheir->right ); - } - - /* Tail */ - while( --top >= 0 ) - { - if( top != 0 ) - { - aatree_node *above = aatree_get_node( tree, up[top-1] ); - dir = above->right == up[top]; - } - - aatree_recount( tree, up[top] ); - aatree_node *pntop = aatree_get_node( tree, up[top] ); - - if( !(pntop->left == AATREE_PTR_NIL || pntop->right == AATREE_PTR_NIL) ) - { - aatree_node *pnl = aatree_get_node( tree, pntop->left ), - *pnr = aatree_get_node( tree, pntop->right ); - - if( pnl->level < pntop->level-1 || pnr->level < pntop->level-1 ) - { - if( pnr->level > --pntop->level ) - pnr->level = pntop->level; - - up[top] = aatree_skew( tree, up[top] ); - - aatree_node *ut = aatree_get_node( tree, up[top] ); - ut->right = aatree_skew( tree, ut->right ); - - aatree_node *utr = aatree_get_node( tree, ut->right ); - utr->right = aatree_skew( tree, utr->right ); - - up[top] = aatree_split( tree, up[top] ); - ut = aatree_get_node( tree, up[top] ); - - ut->right = aatree_split( tree, ut->right ); - } - } - - if( top != 0 ) - { - aatree_node *ut1 = aatree_get_node( tree, up[top-1] ); - - if( dir == 1 ) - aatree_link_down( tree, up[top-1], &ut1->right, up[top] ); - else - aatree_link_down( tree, up[top-1], &ut1->left, up[top] ); - } - else - { - root = up[top]; - aatree_get_node( tree, root )->parent = AATREE_PTR_NIL; - } - } - - /* This is our extension to the original non-recursive delete, so no data - * has to be moved */ - if( _ptrswap_dst != AATREE_PTR_NIL ) - root = aatree_copy_links( tree, root, _ptrswap_src, _ptrswap_dst ); - - return root; -} - -static aatree_ptr aatree_kth( aatree *tree, aatree_ptr t, u32 k ) -{ - u32 i = 0; - - while( t != AATREE_PTR_NIL ) - { - aatree_node *ptnode = aatree_get_node( tree, t ); - - u32 j = i; - if( ptnode->left != AATREE_PTR_NIL ) - j += aatree_get_node( tree, ptnode->left )->count; - - if( j < k ) - { - i = j+1; - t = ptnode->right; - } - else - { - if( j > k ) - { - t = ptnode->left; - } - else - { - return t; - } - } - } - - return AATREE_PTR_NIL; -} - -static aatree_ptr aatree_next( aatree *tree, aatree_ptr x ) -{ - /* if can go left, go left then all the way right, - * else go up, if it was right link accept - */ - - aatree_node *pnode = aatree_get_node( tree, x ); - if( pnode->right != AATREE_PTR_NIL ) - { - aatree_ptr next = pnode->right; - - while(1) - { - aatree_node *pnext = aatree_get_node( tree, next ); - - if( pnext->left != AATREE_PTR_NIL ) - next = pnext->left; - else - return next; - } - } - else - { - aatree_ptr next = x; - - while(1) - { - aatree_node *pnode = aatree_get_node( tree, next ); - - if( pnode->parent == AATREE_PTR_NIL ) - return AATREE_PTR_NIL; - - aatree_node *pabove = aatree_get_node( tree, pnode->parent ); - if( pabove->left == next ) - return pnode->parent; - else - next = pnode->parent; - } - } -} - -static aatree_ptr aatree_find( aatree *tree, aatree_ptr t, void *value ) -{ - while( t != AATREE_PTR_NIL ) - { - int cmp_result = tree->p_cmp( aatree_get_data( tree, t ), value ); - - if( cmp_result == 0 ) - return t; - else - { - aatree_node *ptnode = aatree_get_node( tree, t ); - - if( cmp_result < 0 ) - t = ptnode->left; - else - t = ptnode->right; - } - } - return t; -} - -/* - * Debugging stuff, everything below is scaffholding and will be removed - * ============================================================================= - */ - -static int aatree_verify_split( aatree *tree, aatree_ptr t ) -{ - if( t == AATREE_PTR_NIL ) return 1; - - aatree_node *ptnode = aatree_get_node( tree, t ); - if( ptnode->right == AATREE_PTR_NIL ) return 1; - - aatree_node *prnode = aatree_get_node( tree, ptnode->right ); - if( prnode->right == AATREE_PTR_NIL ) return 1; - - aatree_node *prrnode = aatree_get_node( tree, prnode->right ); - if( ptnode->level == prrnode->level ) - return 0; - - return 1; -} - -static int aatree_verify_skew( aatree *tree, aatree_ptr t ) -{ - if( t == AATREE_PTR_NIL ) return 1; - - aatree_node *ptnode = aatree_get_node( tree, t ); - if( ptnode->left == AATREE_PTR_NIL ) return 1; - - aatree_node *plnode = aatree_get_node( tree, ptnode->left ); - if( plnode->level == ptnode->level ) - return 0; - - return 1; -} - -static int aatree_verify( aatree *tree, aatree_ptr t ) -{ - aatree_node *ptnode = aatree_get_node( tree, t ); - if( ptnode->parent != AATREE_PTR_NIL ) - { - aatree_node *parent = aatree_get_node( tree, ptnode->parent ); - if( !(parent->left == t || parent->right == t) ) - return 0; - } - - if( ptnode->left != AATREE_PTR_NIL ) - if( aatree_get_node( tree, ptnode->left )->parent != t ) - return 0; - if( ptnode->right != AATREE_PTR_NIL ) - if( aatree_get_node( tree, ptnode->right )->parent != t ) - return 0; - - return aatree_verify_skew( tree, t ) && - aatree_verify_split( tree, t ); -} - - -static void aatree_show_r( aatree *tree, aatree_ptr t, int lvl, - void(*p_show)(void *data) ) -{ - if( t != AATREE_PTR_NIL ) - { - aatree_node *ptnode = aatree_get_node( tree, t ); - aatree_show_r( tree, ptnode->left, lvl+1, p_show ); - - void *data = aatree_get_data( tree, t ); - - for( int i=0; iright, lvl+1, p_show ); - } -} - -static void aatree_show( aatree *tree, aatree_ptr t, void(*p_show)(void *data)) -{ - if( t != AATREE_PTR_NIL ) - { - aatree_node *ptnode = aatree_get_node( tree, t ); - aatree_show( tree, ptnode->left, p_show ); - void *data = aatree_get_data( tree, t ); - - for( int i=0; ilevel; i++ ) - { - vg_info( " " ); - } - p_show( data ); - vg_info( " (%d) \n", t ); - - aatree_show( tree, ptnode->right, p_show ); - } -} - -static void aatree_show_counts( aatree *tree, aatree_ptr t, int lvl, int *ln, - int *err, - void(*p_show)(void *data), int show ) -{ - if( lvl > 20 ) - return; - if( t == AATREE_PTR_NIL ) return; - - aatree_node *ptnode = aatree_get_node( tree, t ); - void *data = aatree_get_data( tree, t ); - - aatree_show_counts( tree, ptnode->left, lvl+1, ln, err, p_show, show ); - - if( show ) vg_info( "%03d| ", *ln ); - *ln = *ln +1; - - if( show ) - for( int i=0; ileft != AATREE_PTR_NIL && ptnode->right != AATREE_PTR_NIL ) - printf( "|" ); - if( ptnode->left != AATREE_PTR_NIL && ptnode->right == AATREE_PTR_NIL ) - printf( "/" ); - if( ptnode->left == AATREE_PTR_NIL && ptnode->right != AATREE_PTR_NIL ) - printf( "\\" ); - - printf( " (%d, %d, parent: %d. V: %d, level: %d) \n", t, - ptnode->count, ptnode->parent, - aatree_verify( tree, t ), ptnode->level); - } - - if( !aatree_verify( tree, t ) ) - { - if( show ) - vg_info( "error\n" ); - *err = 1; - } - - aatree_show_counts( tree, ptnode->right, lvl+1, ln, err, p_show, show ); -} - -/* - * Pool allocator utility which can be placed in a union with regular aa nodes. - */ - -static aatree_ptr aatree_init_pool( aatree *info, u32 item_count ) -{ - for( aatree_ptr i=0; inext_free = AATREE_PTR_NIL; - else - pn->next_free = i+1; - } - - return 0; -} - -static aatree_ptr aatree_pool_alloc( aatree *info, aatree_ptr *head ) -{ - if( *head == AATREE_PTR_NIL ) - { - vg_error( "No nodes free in pool allocator!\n" ); - return AATREE_PTR_NIL; - } - else - { - aatree_ptr gap = *head; - *head = aatree_get_pool_node( info, *head )->next_free; - return gap; - } -} - -static void aatree_pool_free( aatree *info, aatree_ptr node, aatree_ptr *head ) -{ - aatree_pool_node *pn = aatree_get_pool_node( info, node ); - pn->next_free = *head; - *head = node; -} - -#endif /* VG_STORE_H */ diff --git a/vg_string.c b/vg_string.c new file mode 100644 index 0000000..1a38479 --- /dev/null +++ b/vg_string.c @@ -0,0 +1,204 @@ +#include "vg_string.h" +#include "vg_platform.h" +#include + +i32 vg_str_storage( vg_str *str ) +{ + if( str->len == -1 ){ + if( str->buffer ){ + vg_str_dynamic *arr = (vg_str_dynamic *)str->buffer; + return (arr-1)->len; + } + else return 0; + } + else return str->len; +} + +/* + * Reset string. If len is -1 (dynamically allocated), buffer must be either + * NULL or be acquired from malloc or realloc + */ +void vg_strnull( vg_str *str, char *buffer, i32 len ) +{ + str->buffer = buffer; + if( buffer ) + str->buffer[0] = '\0'; + + str->i = 0; + str->len = len; + + if( len == 0 ) + vg_fatal_error( "0 length string allocation\n" ); +} + +void vg_strfree( vg_str *str ) +{ + if( str->len == -1 ){ + if( str->buffer ){ + vg_str_dynamic *arr = (vg_str_dynamic *)str->buffer; + free( arr-1 ); + + str->buffer = NULL; + str->i = 0; + } + } +} + +/* + * Double the size of the dynamically allocated string. If unallocated, alloc of + * 16 bytes minimum. + */ +static i32 vg_str_dynamic_grow( vg_str *str ) +{ + if( str->buffer ){ + vg_str_dynamic *hdr = ((vg_str_dynamic *)str->buffer) - 1; + i32 total = (hdr->len + sizeof(vg_str_dynamic)) * 2; + hdr = realloc( hdr, total ); + hdr->len = total - sizeof(vg_str_dynamic); + str->buffer = (char *)(hdr+1); + return hdr->len; + } + else { + vg_str_dynamic *hdr = malloc(16); + hdr->len = 16-sizeof(vg_str_dynamic); + str->buffer = (char *)(hdr+1); + str->buffer[0] = '\0'; + return hdr->len; + } +} + +void vg_strcat( vg_str *str, const char *append ) +{ + if( !append || (str->i == -1) ) return; + + i32 max = vg_str_storage( str ), + i = 0; + +append: + if( str->i == max ){ + if( str->len == -1 ) + max = vg_str_dynamic_grow( str ); + else{ + str->i = -1; + str->buffer[ max-1 ] = '\0'; + return; + } + } + + char c = append[ i ++ ]; + str->buffer[ str->i ] = c; + + if( c == '\0' ) + return; + + str->i ++; + goto append; +} + +void vg_strcatch( vg_str *str, char c ) +{ + vg_strcat( str, (char[]){ c, '\0' } ); +} + +/* + * FIXME: Negative numbers + */ +void vg_strcati32( vg_str *str, i32 value ) +{ + if( value ){ + char temp[32]; + int i=0; + while( value && (i<31) ){ + temp[ i ++ ] = '0' + (value % 10); + value /= 10; + } + + char reverse[32]; + for( int j=0; j=n ) + break; + + temp[ n-1 - (i ++) ] = '0' + (value % 10); + value /= 10; + } + + for( ;ii == -1 ) return 0; + else return 1; +} + +/* + * Returns pointer to last instance of character + */ +char *vg_strch( vg_str *str, char c ) +{ + char *ptr = NULL; + for( i32 i=0; ii; i++ ){ + if( str->buffer[i] == c ) + ptr = str->buffer+i; + } + + return ptr; +} + +u32 vg_strncpy( const char *src, char *dst, u32 len, + enum strncpy_behaviour behaviour ) +{ + for( u32 i=0; ilen == -1 ){ - if( str->buffer ){ - vg_str_dynamic *arr = (vg_str_dynamic *)str->buffer; - return (arr-1)->len; - } - else return 0; - } - else return str->len; -} +i32 vg_str_storage( vg_str *str ); /* * Reset string. If len is -1 (dynamically allocated), buffer must be either * NULL or be acquired from malloc or realloc */ -static void vg_strnull( vg_str *str, char *buffer, i32 len ){ - str->buffer = buffer; - if( buffer ) - str->buffer[0] = '\0'; - - str->i = 0; - str->len = len; - - assert(len); -} - -static void vg_strfree( vg_str *str ){ - if( str->len == -1 ){ - if( str->buffer ){ - vg_str_dynamic *arr = (vg_str_dynamic *)str->buffer; - free( arr-1 ); - - str->buffer = NULL; - str->i = 0; - } - } -} - -/* - * Double the size of the dynamically allocated string. If unallocated, alloc of - * 16 bytes minimum. - */ -static i32 vg_str_dynamic_grow( vg_str *str ){ - if( str->buffer ){ - vg_str_dynamic *hdr = ((vg_str_dynamic *)str->buffer) - 1; - i32 total = (hdr->len + sizeof(vg_str_dynamic)) * 2; - hdr = realloc( hdr, total ); - hdr->len = total - sizeof(vg_str_dynamic); - str->buffer = (char *)(hdr+1); - return hdr->len; - } - else { - vg_str_dynamic *hdr = malloc(16); - hdr->len = 16-sizeof(vg_str_dynamic); - str->buffer = (char *)(hdr+1); - str->buffer[0] = '\0'; - return hdr->len; - } -} +void vg_strnull( vg_str *str, char *buffer, i32 len ); +void vg_strfree( vg_str *str ); /* * Append null terminated string to vg_str */ -static void vg_strcat( vg_str *str, const char *append ){ - if( !append || (str->i == -1) ) return; - - i32 max = vg_str_storage( str ), - i = 0; - -append: - if( str->i == max ){ - if( str->len == -1 ) - max = vg_str_dynamic_grow( str ); - else{ - str->i = -1; - str->buffer[ max-1 ] = '\0'; - return; - } - } - - char c = append[ i ++ ]; - str->buffer[ str->i ] = c; - - if( c == '\0' ) - return; - - str->i ++; - goto append; -} +void vg_strcat( vg_str *str, const char *append ); /* * Append character to vg_str */ -static void vg_strcatch( vg_str *str, char c ){ - vg_strcat( str, (char[]){ c, '\0' } ); -} - -/* - * FIXME: Negative numbers - */ -static void vg_strcati32( vg_str *str, i32 value ){ - if( value ){ - char temp[32]; - int i=0; - while( value && (i<31) ){ - temp[ i ++ ] = '0' + (value % 10); - value /= 10; - } - - char reverse[32]; - for( int j=0; j=n ) - break; - - temp[ n-1 - (i ++) ] = '0' + (value % 10); - value /= 10; - } - - for( ;ii == -1 ) return 0; - else return 1; -} +int vg_strgood( vg_str *str ); /* * Returns pointer to last instance of character */ -static char *vg_strch( vg_str *str, char c ){ - char *ptr = NULL; - for( i32 i=0; ii; i++ ){ - if( str->buffer[i] == c ) - ptr = str->buffer+i; - } +char *vg_strch( vg_str *str, char c ); + +enum strncpy_behaviour +{ + k_strncpy_always_add_null = 0, + k_strncpy_allow_cutoff = 1, + k_strncpy_overflow_fatal = 2 +}; - return ptr; -} +u32 vg_strncpy( const char *src, char *dst, u32 len, + enum strncpy_behaviour behaviour ); +u32 vg_strdjb2( const char *str ); +int vg_strdjb2_eq( const char *s1, u32 h1, const char *s2, u32 h2 ); -#endif /* VG_STRING_H */ +#define VG_STRDJB2_EQ( CS1, S2, H2 ) \ + vg_strdjb2_eq( CS1, vg_strdjb2(CS1), S2, H2 ) diff --git a/vg_tex.c b/vg_tex.c new file mode 100644 index 0000000..1ed8c96 --- /dev/null +++ b/vg_tex.c @@ -0,0 +1,276 @@ +#include "vg_tex.h" +#include "vg_engine.h" +#include "vg_async.h" +#include "vg_io.h" +#include + +static u8 const_vg_tex2d_err[] ={ + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, +}; + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; +static u32 qoi_read_32( const u8 *bytes, int *p ) { + u32 a = bytes[(*p)++]; + u32 b = bytes[(*p)++]; + u32 c = bytes[(*p)++]; + u32 d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +struct texture_load_info{ + GLuint *dest; + u32 width, height, flags; + u8 *rgba; +}; + +static void async_vg_tex2d_upload( void *payload, u32 size ) +{ + if( vg_thread_purpose() != k_thread_purpose_main ){ + vg_fatal_error( "Catastrophic programming error.\n" ); + } + + struct texture_load_info *info = payload; + + glGenTextures( 1, info->dest ); + glBindTexture( GL_TEXTURE_2D, *info->dest ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info->width, info->height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba ); + + if( !(info->flags & VG_TEX2D_NOMIP) ){ + glGenerateMipmap( GL_TEXTURE_2D ); + } + + if( info->flags & VG_TEX2D_LINEAR ){ + if( info->flags & VG_TEX2D_NOMIP ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else{ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR ); + } + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + if( info->flags & VG_TEX2D_NEAREST ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + + if( info->flags & VG_TEX2D_CLAMP ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } + + if( info->flags & VG_TEX2D_REPEAT ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + } +} + +void vg_tex2d_replace_with_error_async( GLuint *dest ) +{ + u32 hdr_size = vg_align8(sizeof(struct texture_load_info)); + + vg_async_item *call = vg_async_alloc( hdr_size ); + struct texture_load_info *info = call->payload; + + info->dest = dest; + info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP; + info->width = 4; + info->height = 4; + info->rgba = const_vg_tex2d_err; + + vg_async_dispatch( call, async_vg_tex2d_upload ); +} + +void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size, + u32 flags, GLuint *dest ) +{ + u32 header_magic; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + u32 channels = 4; /* TODO */ + + qoi_desc desc; + + if ( + bytes == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + vg_error( "Error while decoding qoi file: illegal parameters\n" ); + vg_tex2d_replace_with_error_async( dest ); + return; + } + + header_magic = qoi_read_32(bytes, &p); + desc.width = qoi_read_32(bytes, &p); + desc.height = qoi_read_32(bytes, &p); + desc.channels = bytes[p++]; + desc.colorspace = bytes[p++]; + + if ( + desc.width == 0 || desc.height == 0 || + desc.channels < 3 || desc.channels > 4 || + desc.colorspace > 1 || + header_magic != QOI_MAGIC || + desc.height >= QOI_PIXELS_MAX / desc.width + ) { + vg_error( "Error while decoding qoi file: invalid file\n" ); + vg_tex2d_replace_with_error_async( dest ); + return; + } + + if (channels == 0) { + channels = desc.channels; + } + + px_len = desc.width * desc.height * channels; + + /* allocate async call + * -------------------------- + */ + u32 hdr_size = vg_align8(sizeof(struct texture_load_info)), + tex_size = vg_align8(px_len); + + vg_async_item *call = vg_async_alloc( hdr_size + tex_size ); + struct texture_load_info *info = call->payload; + + info->dest = dest; + info->flags = flags; + info->width = desc.width; + info->height = desc.height; + info->rgba = ((u8*)call->payload) + hdr_size; + + /* + * Decode + * ---------------------------- + */ + + u8 *pixels = info->rgba; + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + + if (channels == 4) { + pixels[px_pos + 3] = px.rgba.a; + } + } + + /* + * Complete the call + * -------------------------- + */ + + vg_async_dispatch( call, async_vg_tex2d_upload ); +} + +void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest ) +{ + if( vg_thread_purpose() != k_thread_purpose_loader ) + vg_fatal_error( "wrong thread\n" ); + + vg_linear_clear( vg_mem.scratch ); + + u32 size; + const void *data = vg_file_read( vg_mem.scratch, path, &size ); + vg_tex2d_load_qoi_async( data, size, flags, dest ); +} diff --git a/vg_tex.h b/vg_tex.h index 3f9cd98..24a99a0 100644 --- a/vg_tex.h +++ b/vg_tex.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved +/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved * * A portion of this file is copied and altered from the QOI projects' source, * Originally written by Dominic Szablewski. It is slightly modified. @@ -30,27 +30,10 @@ SOFTWARE. */ -#ifndef VG_TEX_H -#define VG_TEX_H - -#define VG_GAME -#include "vg/vg.h" -#include "vg/vg_log.h" - -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include "vg/submodules/stb/stb_image_write.h" - -/* its a sad day. */ -#if 0 -#define STBI_MALLOC(X) -#define STBI_REALLOC(X,Y) -#define STBI_FREE(X) -#endif - -#define STBI_ONLY_JPEG -#define STBI_NO_THREAD_LOCALS -#define STB_IMAGE_IMPLEMENTATION -#include "vg/submodules/stb/stb_image.h" +#pragma once +#include "vg_log.h" +#include "vg_image.h" +#include "vg_engine.h" struct vg_sprite { @@ -63,277 +46,12 @@ struct vg_sprite #define VG_TEX2D_CLAMP 0x8 #define VG_TEX2D_NOMIP 0x10 -static u8 const_vg_tex2d_err[] ={ - 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, - 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, - 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, - 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, - 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, - 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, - 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, - 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, -}; - -#define QOI_SRGB 0 -#define QOI_LINEAR 1 - -typedef struct { - unsigned int width; - unsigned int height; - unsigned char channels; - unsigned char colorspace; -} qoi_desc; - -#ifndef QOI_ZEROARR - #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) -#endif - -#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ -#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ -#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ -#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ -#define QOI_OP_RGB 0xfe /* 11111110 */ -#define QOI_OP_RGBA 0xff /* 11111111 */ - -#define QOI_MASK_2 0xc0 /* 11000000 */ - -#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) -#define QOI_MAGIC \ - (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ - ((unsigned int)'i') << 8 | ((unsigned int)'f')) -#define QOI_HEADER_SIZE 14 - -/* 2GB is the max file size that this implementation can safely handle. We guard -against anything larger than that, assuming the worst case with 5 bytes per -pixel, rounded down to a nice clean value. 400 million pixels ought to be -enough for anybody. */ -#define QOI_PIXELS_MAX ((unsigned int)400000000) - -typedef union { - struct { unsigned char r, g, b, a; } rgba; - unsigned int v; -} qoi_rgba_t; +/* options to create texutres; call only from loader thread. + * *dest will be replaced synchronously by the main thread when ready. */ -static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; -static u32 qoi_read_32( const u8 *bytes, int *p ) { - u32 a = bytes[(*p)++]; - u32 b = bytes[(*p)++]; - u32 c = bytes[(*p)++]; - u32 d = bytes[(*p)++]; - return a << 24 | b << 16 | c << 8 | d; -} - -struct texture_load_info{ - GLuint *dest; - u32 width, height, flags; - u8 *rgba; -}; - -static void async_vg_tex2d_upload( void *payload, u32 size ) -{ - if( vg_thread_purpose() != k_thread_purpose_main ){ - vg_fatal_error( "Catastrophic programming error.\n" ); - } - - struct texture_load_info *info = payload; - - glGenTextures( 1, info->dest ); - glBindTexture( GL_TEXTURE_2D, *info->dest ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info->width, info->height, - 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba ); - - if( !(info->flags & VG_TEX2D_NOMIP) ){ - glGenerateMipmap( GL_TEXTURE_2D ); - } - - if( info->flags & VG_TEX2D_LINEAR ){ - if( info->flags & VG_TEX2D_NOMIP ){ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - } - else{ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_LINEAR_MIPMAP_LINEAR ); - } - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - } - - if( info->flags & VG_TEX2D_NEAREST ){ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - } - - if( info->flags & VG_TEX2D_CLAMP ){ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - } - - if( info->flags & VG_TEX2D_REPEAT ){ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); - } -} - -static void vg_tex2d_replace_with_error( GLuint *dest ) -{ - u32 hdr_size = vg_align8(sizeof(struct texture_load_info)); +void vg_tex2d_replace_with_error_async( GLuint *dest ); - vg_async_item *call = vg_async_alloc( hdr_size ); - struct texture_load_info *info = call->payload; - - info->dest = dest; - info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP; - info->width = 4; - info->height = 4; - info->rgba = const_vg_tex2d_err; - - vg_async_dispatch( call, async_vg_tex2d_upload ); -} - -static void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size, - u32 flags, GLuint *dest ) -{ - u32 header_magic; - qoi_rgba_t index[64]; - qoi_rgba_t px; - int px_len, chunks_len, px_pos; - int p = 0, run = 0; - - u32 channels = 4; /* TODO */ - - qoi_desc desc; - - if ( - bytes == NULL || - (channels != 0 && channels != 3 && channels != 4) || - size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) - ) { - vg_error( "Error while decoding qoi file: illegal parameters\n" ); - vg_tex2d_replace_with_error( dest ); - return; - } - - header_magic = qoi_read_32(bytes, &p); - desc.width = qoi_read_32(bytes, &p); - desc.height = qoi_read_32(bytes, &p); - desc.channels = bytes[p++]; - desc.colorspace = bytes[p++]; - - if ( - desc.width == 0 || desc.height == 0 || - desc.channels < 3 || desc.channels > 4 || - desc.colorspace > 1 || - header_magic != QOI_MAGIC || - desc.height >= QOI_PIXELS_MAX / desc.width - ) { - vg_error( "Error while decoding qoi file: invalid file\n" ); - vg_tex2d_replace_with_error( dest ); - return; - } - - if (channels == 0) { - channels = desc.channels; - } - - px_len = desc.width * desc.height * channels; - - /* allocate async call - * -------------------------- - */ - u32 hdr_size = vg_align8(sizeof(struct texture_load_info)), - tex_size = vg_align8(px_len); - - vg_async_item *call = vg_async_alloc( hdr_size + tex_size ); - struct texture_load_info *info = call->payload; - - info->dest = dest; - info->flags = flags; - info->width = desc.width; - info->height = desc.height; - info->rgba = ((u8*)call->payload) + hdr_size; - - /* - * Decode - * ---------------------------- - */ - - u8 *pixels = info->rgba; - - QOI_ZEROARR(index); - px.rgba.r = 0; - px.rgba.g = 0; - px.rgba.b = 0; - px.rgba.a = 255; - - chunks_len = size - (int)sizeof(qoi_padding); - for (px_pos = 0; px_pos < px_len; px_pos += channels) { - if (run > 0) { - run--; - } - else if (p < chunks_len) { - int b1 = bytes[p++]; - - if (b1 == QOI_OP_RGB) { - px.rgba.r = bytes[p++]; - px.rgba.g = bytes[p++]; - px.rgba.b = bytes[p++]; - } - else if (b1 == QOI_OP_RGBA) { - px.rgba.r = bytes[p++]; - px.rgba.g = bytes[p++]; - px.rgba.b = bytes[p++]; - px.rgba.a = bytes[p++]; - } - else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { - px = index[b1]; - } - else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { - px.rgba.r += ((b1 >> 4) & 0x03) - 2; - px.rgba.g += ((b1 >> 2) & 0x03) - 2; - px.rgba.b += ( b1 & 0x03) - 2; - } - else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { - int b2 = bytes[p++]; - int vg = (b1 & 0x3f) - 32; - px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); - px.rgba.g += vg; - px.rgba.b += vg - 8 + (b2 & 0x0f); - } - else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { - run = (b1 & 0x3f); - } - - index[QOI_COLOR_HASH(px) % 64] = px; - } - - pixels[px_pos + 0] = px.rgba.r; - pixels[px_pos + 1] = px.rgba.g; - pixels[px_pos + 2] = px.rgba.b; - - if (channels == 4) { - pixels[px_pos + 3] = px.rgba.a; - } - } - - /* - * Complete the call - * -------------------------- - */ - - vg_async_dispatch( call, async_vg_tex2d_upload ); -} - -static -void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest ) -{ - if( vg_thread_purpose() != k_thread_purpose_loader ) - vg_fatal_error( "wrong thread\n" ); - - vg_linear_clear( vg_mem.scratch ); - - u32 size; - const void *data = vg_file_read( vg_mem.scratch, path, &size ); - vg_tex2d_load_qoi_async( data, size, flags, dest ); -} + u32 flags, GLuint *dest ); -#endif /* VG_TEX_H */ +void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest ); diff --git a/vg_tool.c b/vg_tool.c new file mode 100644 index 0000000..8ac0832 --- /dev/null +++ b/vg_tool.c @@ -0,0 +1,19 @@ +#include "vg_tool.h" + +/* unity build of vg components */ +#include "vg_log.c" +#include "vg_string.c" +#include "vg_opt.c" +#include "vg_msg.c" +#include "vg_mem.c" +#include "vg_mem_queue.c" +#include "vg_io.c" + +void vg_fatal_error( const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + _vg_logx_va( stderr, NULL, "fatal", KRED, fmt, args ); + va_end( args ); + exit(1); +} diff --git a/vg_tool.h b/vg_tool.h new file mode 100644 index 0000000..aa7a850 --- /dev/null +++ b/vg_tool.h @@ -0,0 +1,3 @@ +#pragma once +#include "vg_log.h" +void vg_fatal_error( const char *fmt, ... ); diff --git a/vg_vorbis.h b/vg_vorbis.h new file mode 100644 index 0000000..b27bf4d --- /dev/null +++ b/vg_vorbis.h @@ -0,0 +1,12 @@ +#define STB_VORBIS_HEADER_ONLY +#define STB_VORBIS_MAX_CHANNELS 2 +#include "submodules/stb/stb_vorbis.c" +#include "vg_platform.h" +#include "vg_m.h" + +/* extended by vg */ +int +stb_vorbis_get_samples_float_interleaved_stereo( stb_vorbis *f, float *buffer, + int len ); +int +stb_vorbis_get_samples_i16_downmixed( stb_vorbis *f, i16 *buffer, int len );