build system revision
authorhgn <hgodden00@gmail.com>
Thu, 22 Feb 2024 08:42:39 +0000 (08:42 +0000)
committerhgn <hgodden00@gmail.com>
Thu, 22 Feb 2024 08:42:39 +0000 (08:42 +0000)
87 files changed:
labs/build.c
labs/build.sh
labs/physics.c
vg.h
vg_async.c [new file with mode: 0644]
vg_async.h
vg_audio.c [new file with mode: 0644]
vg_audio.h
vg_audio_dsp.c [new file with mode: 0644]
vg_audio_dsp.h
vg_audio_synth_bird.c [new file with mode: 0644]
vg_audio_synth_bird.h
vg_binstr.c [new file with mode: 0644]
vg_binstr.h
vg_build.h
vg_build_utils_shader.h
vg_bvh.c [new file with mode: 0644]
vg_bvh.h
vg_camera.c [new file with mode: 0644]
vg_camera.h
vg_console.c [new file with mode: 0644]
vg_console.h
vg_depencies.c [new file with mode: 0644]
vg_engine.c [new file with mode: 0644]
vg_engine.h [new file with mode: 0644]
vg_graph.h [deleted file]
vg_image.h [new file with mode: 0644]
vg_imgui.c [new file with mode: 0644]
vg_imgui.h
vg_input.c [new file with mode: 0644]
vg_input.h
vg_io.c [new file with mode: 0644]
vg_io.h
vg_lines.c [new file with mode: 0644]
vg_lines.h
vg_loader.c [new file with mode: 0644]
vg_loader.h
vg_log.c
vg_log.h
vg_m.h
vg_m.hc [new file with mode: 0644]
vg_mem.c [new file with mode: 0644]
vg_mem.h
vg_mem_pool.c [new file with mode: 0644]
vg_mem_pool.h
vg_mem_queue.c [new file with mode: 0644]
vg_mem_queue.h
vg_msg.c [new file with mode: 0644]
vg_msg.h
vg_opt.c [new file with mode: 0644]
vg_opt.h
vg_perlin.c [new file with mode: 0644]
vg_perlin.h
vg_platform.h
vg_profiler.c [new file with mode: 0644]
vg_profiler.h
vg_rigidbody.c [new file with mode: 0644]
vg_rigidbody.h
vg_rigidbody_collision.c [new file with mode: 0644]
vg_rigidbody_collision.h
vg_rigidbody_constraints.c [new file with mode: 0644]
vg_rigidbody_constraints.h
vg_rigidbody_view.c [new file with mode: 0644]
vg_rigidbody_view.h
vg_settings_menu.h [deleted file]
vg_shader.c [new file with mode: 0644]
vg_shader.h
vg_stdint.h [deleted file]
vg_steam.c [new file with mode: 0644]
vg_steam.h
vg_steam_api.h [new file with mode: 0644]
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_store.h [deleted file]
vg_string.c [new file with mode: 0644]
vg_string.h
vg_tex.c [new file with mode: 0644]
vg_tex.h
vg_tool.c [new file with mode: 0644]
vg_tool.h [new file with mode: 0644]
vg_vorbis.h [new file with mode: 0644]

index bb0b343c8ae4eec5391c7255c2c25f3ce680931b..973f420e3c7ed74e97505eecf1d0a6fcfb7d63aa 100644 (file)
@@ -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" );
 }
index d807648f5c4c0b8567c1d98974b8037da16bbe8d..f5175ea5c3f07c82faa0ea13608506a7bce68558 100755 (executable)
@@ -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 $@
index 7373660b9fcac4dc9bba1c116b5e693e03badb28..c570a31d235c18f600c0874ee32ad90fff1daf0a 100644 (file)
@@ -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 119e9b1b162b54ebe4e24ddf7c9c63ded761c2f8..2d06aab83aadfa96a98b127be863b4445614ce1f 100644 (file)
--- a/vg.h
+++ b/vg.h
-/* 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 (file)
index 0000000..0d7809d
--- /dev/null
@@ -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 );
+}
index b3c8a0af09fecd21c28f505c7bdc4d46e05c0426..e0a252806b6dbdf02bf583e10d6cfce151f173ab 100644 (file)
@@ -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 (file)
index 0000000..1611a62
--- /dev/null
@@ -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 <string.h>
+
+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; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      if( !ch->allocated ){
+         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; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      if( ch->allocated ){
+         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; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+      if( ch->allocated && (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; i<count; i++ ){
+      dst[ i*2 + 0 ] = ((float)src[i]) * (1.0f/32767.0f);
+      dst[ i*2 + 1 ] = ((float)src[i]) * (1.0f/32767.0f);
+   }
+}
+
+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;i<samples_this_run; i++ ){
+            dst[i*2+0] = 0.0f;
+            dst[i*2+1] = 0.0f;
+         }
+      }
+      else if( format == k_audio_format_vorbis ){
+         int read_samples = stb_vorbis_get_samples_float_interleaved_stereo( 
+               ch->handle.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; i<samples_this_run; i++ ){
+               dst[i*2+0] = 0.0f;
+               dst[i*2+1] = 0.0f;
+            }
+         }
+      }
+      else if( format == k_audio_format_bird ){
+         synth_bird_generate_samples( ch->handle.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<AUDIO_MIX_FRAME_SIZE; j++ ){
+      volume_movement += 1.0f;
+      float movement_t = volume_movement * inv_volume_rate;
+            movement_t = vg_minf( movement_t, 1.0f );
+      volume           = vg_lerpf( volume_start, volume_target, movement_t );
+
+      float vol_norm = volume * volume;
+
+      if( ch->_.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; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      if( !ch->allocated )
+         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; i<AUDIO_LFOS; i++ ){
+      audio_lfo *lfo = &vg_audio.oscillators[ i ];
+
+      if( lfo->editble_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; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      if( ch->activity == 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; i<frame_count*2; i ++ )
+      pOut32F[i] = 0.0f;
+
+   for( int i=0; i<AUDIO_LFOS; i++ ){
+      audio_lfo *lfo = &vg_audio.oscillators[i];
+      lfo->time_startframe = lfo->time;
+   }
+
+   for( int i=0; i<AUDIO_CHANNELS; i ++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      if( ch->activity == 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; i<frame_count; i++ )
+         vg_dsp_process( pOut32F + i*2, pOut32F + i*2 );
+      vg_profile_end( &_vg_prof_dsp );
+   }
+
+   audio_lock();
+
+   for( int i=0; i<AUDIO_CHANNELS; i ++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+      ch->readable_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; i<count; i++ )
+      audio_clip_load( &arr[i], lin_alloc );
+}
+
+static void audio_require_clip_loaded( audio_clip *clip )
+{
+   if( clip->data && 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; i<AUDIO_CHANNELS; i ++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      ui_rect row;
+      ui_split( window, k_ui_axis_h, 18, 1, row, window );
+
+      if( !ch->allocated ){
+         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<overlap_length; k++ ){
+                  ui_px *wk = overlap_buffer[k];
+                  if( ((wr[0] <= wk[0]+wk[2]) && (wr[0]+wr[2] >= 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();
+}
index 0290e85fb9777a3151239f8eef38d6896997b01c..6272cafccbe29ef41e546abc0fcf57c264fa051b 100644 (file)
@@ -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; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      if( !ch->allocated ){
-         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; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      if( ch->allocated ){
-         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; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-      if( ch->allocated && (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; i<count; i++ ){
-      dst[ i*2 + 0 ] = ((float)src[i]) * (1.0f/32767.0f);
-      dst[ i*2 + 1 ] = ((float)src[i]) * (1.0f/32767.0f);
-   }
-}
-
-/* 
- * adapted from stb_vorbis.h, since the original does not handle mono->stereo
- */
-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;i<samples_this_run; i++ ){
-            dst[i*2+0] = 0.0f;
-            dst[i*2+1] = 0.0f;
-         }
-      }
-      else if( format == k_audio_format_vorbis ){
-         int read_samples = stb_vorbis_get_samples_float_interleaved_stereo( 
-               ch->vorbis_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; i<samples_this_run; i++ ){
-               dst[i*2+0] = 0.0f;
-               dst[i*2+1] = 0.0f;
-            }
-         }
-      }
-      else if( format == k_audio_format_bird ){
-         synth_bird_generate_samples( ch->bird_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<AUDIO_MIX_FRAME_SIZE; j++ ){
-      volume_movement += 1.0f;
-      float movement_t = volume_movement * inv_volume_rate;
-            movement_t = vg_minf( movement_t, 1.0f );
-      volume           = vg_lerpf( volume_start, volume_target, movement_t );
-
-      float vol_norm = volume * volume;
-
-      if( ch->_.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; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      if( !ch->allocated )
-         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; i<AUDIO_LFOS; i++ ){
-      audio_lfo *lfo = &vg_audio.oscillators[ i ];
-
-      if( lfo->editble_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; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      if( ch->activity == 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; i<frame_count*2; i ++ )
-      pOut32F[i] = 0.0f;
-
-   for( int i=0; i<AUDIO_LFOS; i++ ){
-      audio_lfo *lfo = &vg_audio.oscillators[i];
-      lfo->time_startframe = lfo->time;
-   }
-
-   for( int i=0; i<AUDIO_CHANNELS; i ++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      if( ch->activity == 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; i<frame_count; i++ )
-         vg_dsp_process( pOut32F + i*2, pOut32F + i*2 );
-      vg_profile_end( &_vg_prof_dsp );
-   }
-
-   audio_lock();
-
-   for( int i=0; i<AUDIO_CHANNELS; i ++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-      ch->readable_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; i<count; i++ )
-      audio_clip_load( &arr[i], lin_alloc );
-}
-
-static void audio_require_clip_loaded( audio_clip *clip )
-{
-   if( clip->data && 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; i<AUDIO_CHANNELS; i ++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      ui_rect row;
-      ui_split( window, k_ui_axis_h, 18, 1, row, window );
-
-      if( !ch->allocated ){
-         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<overlap_length; k++ ){
-                  ui_px *wk = overlap_buffer[k];
-                  if( ((wr[0] <= wk[0]+wk[2]) && (wr[0]+wr[2] >= 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 (file)
index 0000000..3714e32
--- /dev/null
@@ -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; i<delay->length; 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;
+   }
+}
index c2bed10ebde2a7935fc2c53ddf8167743bb14350..d74bc0229197b873b877d101a76a4ead4eba7a04 100644 (file)
@@ -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; i<delay->length; 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 (file)
index 0000000..eecad3b
--- /dev/null
@@ -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; i<bird->settings.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; _<samples; _++ )
+   {
+      synth_bird_think( bird );
+
+      int_add_mod( &bird->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; j<BIRD_SAMPLE_RATE/(32*8*2); j++ )
+         synth_bird_think( bird );
+
+      float hz = bird->rt.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
index ff6ef50153c1f99a13bf132c624f70fc535a10ad..0a2f418a2ba4d373c5bbaf83e93be5b2e72fd194 100644 (file)
@@ -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; i<bird->settings.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; _<samples; _++ )
-   {
-      synth_bird_think( bird );
-
-      int_add_mod( &bird->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; j<BIRD_SAMPLE_RATE/(32*8*2); j++ )
-         synth_bird_think( bird );
-
-      float hz = bird->rt.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 (file)
index 0000000..7d9ac83
--- /dev/null
@@ -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<size/2; i++ ){
+      dst[i]  = (src[i*2+0]-VG_BINSTR_BASECHAR);
+      dst[i] |= (src[i*2+1]-VG_BINSTR_BASECHAR)<<4u;
+   }
+}
+
+void vg_bin_str( const void *bin, void *txt, u32 size )
+{
+         u8 *dst = txt;
+   const u8 *src = bin;
+
+   for( u32 i=0; i<size; i++ ){
+      dst[i*2+0] = VG_BINSTR_BASECHAR + ((src[i]    ) & 0xf);
+      dst[i*2+1] = VG_BINSTR_BASECHAR + ((src[i]>>4u) & 0xf);
+   }
+}
index d6bfb83b40cff72b75352226a89b9c816d851800..6e662fe3f33dd11f04e74b813fdd6297fd2ff675 100644 (file)
@@ -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<size/2; i++ ){
-      dst[i]  = (src[i*2+0]-VG_BINSTR_BASECHAR);
-      dst[i] |= (src[i*2+1]-VG_BINSTR_BASECHAR)<<4u;
-   }
-}
-
-static void vg_bin_str( const void *bin, void *txt, u32 size )
-{
-         u8 *dst = txt;
-   const u8 *src = bin;
-
-   for( u32 i=0; i<size; i++ ){
-      dst[i*2+0] = VG_BINSTR_BASECHAR + ((src[i]    ) & 0xf);
-      dst[i*2+1] = VG_BINSTR_BASECHAR + ((src[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 );
index a30fe17f9b73e57852db3c29bd7478a7cf80c6f9..9e5d9a7dcab17db3bead2b3a63c5834db6cd3b71 100644 (file)
 #include <stdio.h>
 #include <unistd.h>
 #include <time.h>
+#include <stdarg.h>
 
-#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<path> */
+          library,      /* -L<path> */
+          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", "" );
 }
index d1d2d05c9fd3cf242004ef4d7ab15a12cc6e3dee..5747cf9729e25722a6f0193bcd32f52934c91549 100644 (file)
@@ -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 (file)
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; i<node->count; 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; t<node->count; 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; i<node->count; 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; i<inode->count; 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;
+}
index 85790c5d58b310f35d2a319036891123cb37706f..37b100aa7c689394e1c834161162dfbe746a071d 100644 (file)
--- 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; i<node->count; 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; t<node->count; 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; i<node->count; 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; i<inode->count; 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 (file)
index 0000000..8cd261f
--- /dev/null
@@ -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 );
+}
index 9fd660bbd0ac1c133191b3f8685fa1c3b35e62ab..45d34440730791214b6065b24ecbd95f06345a53 100644 (file)
@@ -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 (file)
index 0000000..ccc39f7
--- /dev/null
@@ -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 <string.h>
+
+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; i<vg_console.function_count; i ++ ){
+               struct vg_cmd *cmd = &vg_console.functions[ i ];
+               vg_info( "* %s\n", cmd->name );
+       }
+       
+       for( int i=0; i<vg_console.var_count; i ++ ){
+               struct vg_var *cv = &vg_console.vars[ i ];
+               vg_info( "%s %s\n", 
+               (const char *[]){ "i32","u32","f32","str" }[cv->data_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; i<vg_console.var_count; i ++ ){
+               struct vg_var *cv = &vg_console.vars[i];
+
+               if( cv->flags & 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; i<vg_console.var_count; i ++ ){
+               struct vg_var *cv = &vg_console.vars[ i ];
+               if( !strcmp( cv->name, kw ) ){
+         return cv;
+      }
+   }
+
+   return NULL;
+}
+
+vg_cmd *vg_console_match_cmd( const char *kw )
+{
+       for( int i=0; i<vg_console.function_count; i ++ ){
+               struct vg_cmd *cmd = &vg_console.functions[ i ];
+               if( !strcmp( cmd->name, 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; i<arg_count; i ++ ){
+               vg_strcat( str, args[i] );
+               if( i!=arg_count-1 ) vg_strcatch( str, ' ' );
+            }
+         }
+      }
+      else{
+         if( cv->data_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<m; i++ ){
+      costs[0] = i+1;
+
+      u32 corner = i;
+
+      for( u32 j=0; j<n; j++ ){
+         u32 upper = costs[j+1];
+
+         if( s1[i] == s2[j] )
+            costs[ j+1 ] = corner;
+         else{
+            u32 t = (upper < corner)? upper: corner;
+            costs[j+1] = ((costs[j] < t)? costs[j]: t) + 1;
+         }
+
+         corner = upper;
+      }
+   }
+
+   return costs[n];
+}
+
+u32 str_lcs( const char *s1, const char *s2 ){
+   u32 m = VG_MIN( 31, strlen( s1 ) ),
+       n = VG_MIN( 31, strlen( s2 ) );
+
+   int suff[32][32],
+       result = 0;
+
+   for( int i=0; i<=m; i++ ){
+      for( int j=0; j<=n; j++ ){
+         if( i == 0 || j == 0 )
+            suff[i][j] = 0;
+         else if( s1[i-1] == s2[j-1] ){
+            suff[i][j] = suff[i-1][j-1] + 1;
+            result = VG_MAX( result, suff[i][j] );
+         }
+         else
+            suff[i][j] = 0;
+      }
+   }
+   
+   return result;
+}
+
+/* str must not fuckoff ever! */
+void console_suggest_score_text( const char *str, const char *input,
+                                 int minscore )
+{
+   /* filter duplicates */
+   for( int i=0; i<vg_console.suggestion_count; i++ )
+      if( !strcmp( vg_console.suggestions[i].str, str ) )
+         return;
+
+   /* calc score */
+   u32 score = str_lcs( str, input );
+
+   if( score < minscore )
+      return;
+
+   int best_pos = vg_console.suggestion_count;
+   for( int j=best_pos-1; j>=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; i<vg_console.var_count; i++ ){
+         vg_var *cvar = &vg_console.vars[i];
+         console_suggest_score_text( cvar->name, args[0], 1 );
+      }
+
+      for( int i=0; i<vg_console.function_count; i++ ){
+         vg_cmd *cmd = &vg_console.functions[i];
+         console_suggest_score_text( cmd->name, 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; i++ ){
+      vg_console.suggestion_maxlen = VG_MAX( vg_console.suggestion_maxlen,
+                                             vg_console.suggestions[i].len );
+
+      if( vg_console.suggestions[i].lev_score <
+          vg_console.suggestions[0].lev_score/2 )
+      {
+         vg_console.suggestion_count = i;
+         return;
+      }
+   }
+}
+
+/*
+ * Suggestion controls
+ */
+static void _console_fetch_suggestion(void)
+{
+   char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
+
+   if( vg_console.suggestion_select == -1 ){
+      strcpy( target, vg_console.input_copy );
+      _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
+                               &vg_ui.textbox.cursor_pos, 10000, 1 );
+   }
+   else{
+      strncpy( target,
+            vg_console.suggestions[ vg_console.suggestion_select ].str,
+            vg_list_size( vg_console.input )-1 );
+
+      _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
+                               &vg_ui.textbox.cursor_pos, 10000, 1 );
+      _ui_textbox_put_char( ' ' );
+   }
+}
+
+static void _console_suggest_store_normal(void)
+{
+   if( vg_console.suggestion_select == -1 ){
+      char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
+      strcpy( vg_console.input_copy, target );
+   }
+}
+
+static void console_suggest_next(void)
+{
+   if( vg_console.suggestion_count ){
+      _console_suggest_store_normal();
+
+      vg_console.suggestion_select ++;
+
+      if( vg_console.suggestion_select >= 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; i<console_lines; i ++ ){
+      ptr --;
+
+      if( ptr < 0 ) ptr = vg_list_size( vg_log.log )-1;
+      
+      ui_text( rect_line, vg_log.log[ptr], 1, k_ui_align_left, 0 );
+      rect_line[1] -= fh;
+   }
+       
+   /* 
+    * Input area 
+    */
+
+   struct ui_textbox_callbacks callbacks = {
+      .up = _vg_console_on_up,
+      .down = _vg_console_on_down,
+      .change = _vg_console_on_update,
+      .enter = _vg_console_on_enter,
+   };
+   ui_textbox( rect_input, NULL, 
+               vg_console.input, vg_list_size(vg_console.input), 1,
+               UI_TEXTBOX_AUTOFOCUS, &callbacks );
+
+   /* 
+    * suggestions 
+    */
+   if( vg_console.suggestion_count ){
+      ui_rect rect_suggest;
+      rect_copy( rect_input, rect_suggest );
+
+      rect_suggest[0] += 6 + vg_ui.font->spacing*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_console.suggestion_count; i ++ ){
+         u32 text_colour;
+         if( i == vg_console.suggestion_select ){
+            ui_fill( rect_suggest, ui_colour( k_ui_orange ) );
+            text_colour = ui_colourcont( k_ui_orange );
+         }
+         else text_colour = ui_colourcont( k_ui_bg );
+
+         ui_text( rect_suggest, vg_console.suggestions[i].str, 1,
+                  k_ui_align_left, text_colour );
+
+         rect_suggest[1] += fh;
+      }
+   }
+
+   SDL_AtomicUnlock( &vg_log.print_sl );
+}
index 029b3e69efe417011ead5965a481b7c93abd201c..c24423a778de4ad9ddc9afb40a986b0e5776f614 100644 (file)
@@ -1,17 +1,7 @@
-/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
+/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */
 
-/* TODO: String types using dynamic, vg_str! */
-
-#ifndef VG_CONSOLE_H
-#define VG_CONSOLE_H
-
-#ifndef VG_GAME
-  #define VG_GAME
-#endif
-
-#include "vg_imgui.h"
-#include "vg_log.h"
-#include "vg_string.h"
+#pragma once
+#include "vg_platform.h"
 
 #define VG_VAR_F32( NAME, ... ) \
    { u32 flags=0x00; __VA_ARGS__ ;\
@@ -27,7 +17,8 @@
 typedef struct vg_var vg_var;
 typedef struct vg_cmd vg_cmd;
 
-struct vg_console{
+struct vg_console
+{
        struct vg_var{
                void *data;
                const char *name;
@@ -72,688 +63,23 @@ struct vg_console{
        
        i32 enabled, cheats;
 }
-static vg_console;
+extern vg_console;
 
-static void _vg_console_draw( void );
-void           _vg_console_println( const char *str );
-static int  _vg_console_list( int argc, char const *argv[] );
-static void _vg_console_init(void);
-static void _vg_console_write_persistent(void);
-static void _vg_console_free(void);
-static void  vg_execute_console_input( const char *cmd, bool silent );
-
-/*
- * Console interface
- */
-static void console_history_get( char* buf, int entry_num );
-static int _vg_console_enabled(void);
-static void console_proc_key( SDL_Keysym ev );
-
-/*
- * Implementation
- */
-static int _vg_console_enabled(void){ 
-       return vg_console.enabled; 
-}
-
-static 
 void vg_console_reg_var( const char *alias, void *ptr, enum vg_var_dtype type, 
-                         u32 flags )
-{
-   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;
+                         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; i<vg_console.function_count; i ++ ){
-               struct vg_cmd *cmd = &vg_console.functions[ i ];
-               vg_info( "* %s\n", cmd->name );
-       }
-       
-       for( int i=0; i<vg_console.var_count; i ++ ){
-               struct vg_var *cv = &vg_console.vars[ i ];
-               vg_info( "%s %s\n", 
-               (const char *[]){ "i32","u32","f32","str" }[cv->data_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; i<vg_console.var_count; i ++ ){
-               struct vg_var *cv = &vg_console.vars[i];
-
-               if( cv->flags & 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; i<vg_console.var_count; i ++ ){
-               struct vg_var *cv = &vg_console.vars[ i ];
-               if( !strcmp( cv->name, kw ) ){
-         return cv;
-      }
-   }
-
-   return NULL;
-}
-
-static vg_cmd *vg_console_match_cmd( const char *kw )
-{
-       for( int i=0; i<vg_console.function_count; i ++ ){
-               struct vg_cmd *cmd = &vg_console.functions[ i ];
-               if( !strcmp( cmd->name, 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; i<arg_count; i ++ ){
-               vg_strcat( str, args[i] );
-               if( i!=arg_count-1 ) vg_strcatch( str, ' ' );
-            }
-         }
-      }
-      else{
-         if( cv->data_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<m; i++ ){
-      costs[0] = i+1;
-
-      u32 corner = i;
-
-      for( u32 j=0; j<n; j++ ){
-         u32 upper = costs[j+1];
-
-         if( s1[i] == s2[j] )
-            costs[ j+1 ] = corner;
-         else{
-            u32 t = (upper < corner)? upper: corner;
-            costs[j+1] = ((costs[j] < t)? costs[j]: t) + 1;
-         }
-
-         corner = upper;
-      }
-   }
-
-   return costs[n];
-}
-
-u32 str_lcs( const char *s1, const char *s2 ){
-   u32 m = VG_MIN( 31, strlen( s1 ) ),
-       n = VG_MIN( 31, strlen( s2 ) );
-
-   int suff[32][32],
-       result = 0;
-
-   for( int i=0; i<=m; i++ ){
-      for( int j=0; j<=n; j++ ){
-         if( i == 0 || j == 0 )
-            suff[i][j] = 0;
-         else if( s1[i-1] == s2[j-1] ){
-            suff[i][j] = suff[i-1][j-1] + 1;
-            result = VG_MAX( result, suff[i][j] );
-         }
-         else
-            suff[i][j] = 0;
-      }
-   }
-   
-   return result;
-}
-
-/* str must not fuckoff ever! */
-static void console_suggest_score_text( const char *str, const char *input,
-                                           int minscore )
-{
-   /* filter duplicates */
-   for( int i=0; i<vg_console.suggestion_count; i++ )
-      if( !strcmp( vg_console.suggestions[i].str, str ) )
-         return;
-
-   /* calc score */
-   u32 score = str_lcs( str, input );
-
-   if( score < minscore )
-      return;
-
-   int best_pos = vg_console.suggestion_count;
-   for( int j=best_pos-1; j>=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; i<vg_console.var_count; i++ ){
-         vg_var *cvar = &vg_console.vars[i];
-         console_suggest_score_text( cvar->name, args[0], 1 );
-      }
-
-      for( int i=0; i<vg_console.function_count; i++ ){
-         vg_cmd *cmd = &vg_console.functions[i];
-         console_suggest_score_text( cmd->name, 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; i++ ){
-      vg_console.suggestion_maxlen = VG_MAX( vg_console.suggestion_maxlen,
-                                             vg_console.suggestions[i].len );
-
-      if( vg_console.suggestions[i].lev_score <
-          vg_console.suggestions[0].lev_score/2 )
-      {
-         vg_console.suggestion_count = i;
-         return;
-      }
-   }
-}
-
-/*
- * Suggestion controls
- */
-static void _console_fetch_suggestion(void)
-{
-   char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
-
-   if( vg_console.suggestion_select == -1 ){
-      strcpy( target, vg_console.input_copy );
-      _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
-                               &vg_ui.textbox.cursor_pos, 10000, 1 );
-   }
-   else{
-      strncpy( target,
-            vg_console.suggestions[ vg_console.suggestion_select ].str,
-            vg_list_size( vg_console.input )-1 );
-
-      _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
-                               &vg_ui.textbox.cursor_pos, 10000, 1 );
-      _ui_textbox_put_char( ' ' );
-   }
-}
-
-static void _console_suggest_store_normal(void)
-{
-   if( vg_console.suggestion_select == -1 ){
-      char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
-      strcpy( vg_console.input_copy, target );
-   }
-}
-
-static void _console_suggest_next(void)
-{
-   if( vg_console.suggestion_count ){
-      _console_suggest_store_normal();
-
-      vg_console.suggestion_select ++;
-
-      if( vg_console.suggestion_select >= 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; i<console_lines; i ++ ){
-      ptr --;
-
-      if( ptr < 0 ) ptr = vg_list_size( vg_log.log )-1;
-      
-      ui_text( rect_line, vg_log.log[ptr], 1, k_ui_align_left, 0 );
-      rect_line[1] -= fh;
-   }
-       
-   /* 
-    * Input area 
-    */
-
-   struct ui_textbox_callbacks callbacks = {
-      .up = _vg_console_on_up,
-      .down = _vg_console_on_down,
-      .change = _vg_console_on_update,
-      .enter = _vg_console_on_enter,
-   };
-   ui_textbox( rect_input, NULL, 
-               vg_console.input, vg_list_size(vg_console.input), 1,
-               UI_TEXTBOX_AUTOFOCUS, &callbacks );
-
-   /* 
-    * suggestions 
-    */
-   if( vg_console.suggestion_count ){
-      ui_rect rect_suggest;
-      rect_copy( rect_input, rect_suggest );
-
-      rect_suggest[0] += 6 + vg_ui.font->spacing*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_console.suggestion_count; i ++ ){
-         u32 text_colour;
-         if( i == vg_console.suggestion_select ){
-            ui_fill( rect_suggest, ui_colour( k_ui_orange ) );
-            text_colour = ui_colourcont( k_ui_orange );
-         }
-         else text_colour = ui_colourcont( k_ui_bg );
-
-         ui_text( rect_suggest, vg_console.suggestions[i].str, 1,
-                  k_ui_align_left, text_colour );
-
-         rect_suggest[1] += fh;
-      }
-   }
-
-   SDL_AtomicUnlock( &log_print_sl );
-}
-
-
-#endif /* VG_CONSOLE_H */
+                         void (*poll_suggest)(int argc, const char *argv[]) );
+void vg_console_load_autos(void);
+void vg_console_draw(void);
+void vg_console_init(void);
+int vg_console_exec( int argc, const char *argv[] );
+void vg_execute_console_input( const char *cmd, bool silent );
+static void vg_console_write_persistent(void);
+void console_suggest_score_text( const char *str, const char *input,
+                                 int minscore );
+vg_var *vg_console_match_var( const char *kw );
+vg_cmd *vg_console_match_cmd( const char *kw );
+static void console_suggest_next(void);
+static void console_suggest_prev(void);
diff --git a/vg_depencies.c b/vg_depencies.c
new file mode 100644 (file)
index 0000000..318bd9c
--- /dev/null
@@ -0,0 +1,83 @@
+#include "vg_vorbis.h"
+#include "vg_image.h"
+
+#undef STB_VORBIS_HEADER_ONLY
+#include "submodules/stb/stb_vorbis.c"
+#undef L
+#undef R
+#undef C
+
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#include "vg/submodules/stb/stb_image.h"
+#include "vg/submodules/stb/stb_image_write.h"
+
+/* 
+ * adapted from stb_vorbis.h, since the original does not handle mono->stereo
+ */
+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 (file)
index 0000000..0d8c015
--- /dev/null
@@ -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 <string.h>
+
+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<len; i ++ ){
+      if( ((buf[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; i<vg_settings.audio_devices.option_count; i ++ ){
+            oi = &vg_settings.audio_devices.options[i];
+
+            if( oi->value == 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; i<count; i ++ ){
+      struct ui_enum_opt *oi = &options[i+1];
+
+      const char *device_name = SDL_GetAudioDeviceName( i, 0 );
+      int len = strlen(device_name);
+
+      oi->alias = 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; i<count; i ++ ){
+         struct ui_enum_opt *oi = &options[i+1];
+         if( !strcmp( oi->alias, 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 (file)
index 0000000..81bbda5
--- /dev/null
@@ -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 <setjmp.h>
+
+/* 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 (file)
index ec7f974..0000000
+++ /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 (file)
index 0000000..192909e
--- /dev/null
@@ -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 (file)
index 0000000..2510404
--- /dev/null
@@ -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 <string.h>
+
+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_list_size(mesh); i++ ){
+      indices[i] = start+mesh[i];
+   }
+
+       vg_ui.cur_indice += 6;
+       vg_ui.cur_vert += 4;
+}
+
+void ui_fill( ui_rect rect, u32 colour )
+{
+   ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
+}
+
+void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask )
+{
+   /* this if far from ideal but stops us from crashing */
+   if( (vg_ui.cur_vert + 8 > 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<vg_list_size(mesh)/6; i++ ){
+      if( (0x1<<i) & mask ){
+         for( u32 j=0; j<6; j++ )
+            indices[c ++] = start+mesh[i*6+j];
+      }
+   }
+       
+       vg_ui.cur_indice += c;
+       vg_ui.cur_vert += 8;
+}
+
+void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap,
+               ui_rect l, ui_rect r ){
+   enum ui_axis dir = other ^ 0x1;
+
+   if( width < 0 ) width = rect[ 2+dir ] + width;
+
+   ui_rect temp;
+   rect_copy( rect, temp );
+
+   l[ dir ]     = temp[ dir ];
+   r[ dir ]     = temp[ dir ] + width + (gap/2);
+   l[ other ]   = temp[ other ];
+   r[ other ]   = temp[ other ];
+   l[ 2+dir ]   = width                 - (gap/2);
+   r[ 2+dir ]   = temp[ 2+dir ] - width - (gap/2);
+   l[ 2+other ] = temp[ 2+other ];
+   r[ 2+other ] = temp[ 2+other ];
+}
+
+void ui_rect_center( ui_rect parent, ui_rect rect )
+{
+   rect[0] = parent[0] + (parent[2]-rect[2])/2;
+   rect[1] = parent[1] + (parent[3]-rect[3])/2;
+}
+
+void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d )
+{
+   i32 rp = (i32)rect[2] * (i32)size[1],
+       rc = (i32)size[0] * (i32)rect[3];
+
+   enum ui_axis dir, other;
+   if( rc > 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 a<b?a:b; }
+static ui_px ui_max( ui_px a, ui_px b ){ return a>b?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; i<len; i ++ ){
+      if( *value == options[i].value ){
+         display = options[i].alias;
+         valid = 1;
+         break;
+      }
+   }
+
+   if( ui_button_text( box, display, k_ui_scale ) == 1 ){
+      vg_ui.focused_control_type = k_ui_control_enum;
+      vg_ui.ptr_enum = value;
+      vg_ui._enum.option_count = len;
+      vg_ui._enum.options = options;
+      rect_copy( box, vg_ui._enum.rect );
+   }
+
+   if( !valid )
+      ui_outline( box, 1, ui_colour(k_ui_red), 0 );
+}
+
+static void ui_enum_post(void){
+   ui_rect drawer;
+   rect_copy( vg_ui._enum.rect, drawer );
+   drawer[3] *= vg_ui._enum.option_count;
+
+   int close = 0;
+   int clickany= ui_click_up(UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE),
+       hover   = ui_inside_rect( drawer, vg_ui.mouse );
+
+   if( clickany && !hover ){
+      return;
+   }
+
+   /* HACK */
+   vg_ui.focused_control_type = k_ui_control_none;
+   i32 *value = vg_ui.ptr_enum;
+
+   for( u32 i=0; i<vg_ui._enum.option_count; i++ ){
+      ui_rect button;
+      ui_split( drawer, k_ui_axis_h, vg_ui._enum.rect[3], 0, button,drawer );
+      
+      enum ui_scheme_colour colour = k_ui_bg+3;
+      if( vg_ui._enum.options[i].value == *value ) 
+         colour = k_ui_orange;
+
+      if( ui_colourbutton_text( button, vg_ui._enum.options[i].alias, 
+                                k_ui_scale, colour ) == 1 ){
+         *value = vg_ui._enum.options[i].value;
+         close = 1;
+      }
+   }
+
+   /* HACK */
+   vg_ui.focused_control_type = k_ui_control_enum;
+
+   if( !close )
+      vg_ui.focused_control_hit = 1;
+}
+
+/*
+ * Slider
+ * -----------------------------------------------------------------------------
+ */
+
+static enum ui_button_state _ui_slider( 
+      ui_rect box, f32 min, f32 max, f32 *value, const char *format )
+{
+   f32 t;
+
+   enum ui_button_state 
+      mask_using = 
+         k_ui_button_holding_inside |
+         k_ui_button_holding_outside |
+         k_ui_button_click,
+      mask_brighter =
+         mask_using | k_ui_button_hover,
+      state = ui_colourbutton( box, k_ui_bg, k_ui_bg+2, k_ui_bg+3, 1 );
+
+   if( state & mask_using ){
+      t = vg_clampf( (f32)(vg_ui.mouse[0] - box[0]) / (f32)( box[2] ),
+                     0.0f, 1.0f );
+      *value = vg_lerpf( min, max, t );
+   }
+   else
+      t = vg_clampf( (*value - min) / (max-min), 0.0f, 1.0f );
+   
+   ui_rect line     = { box[0], box[1], t * (f32)box[2], box[3] };
+   ui_fill( line, ui_colour(state&mask_brighter? k_ui_bg+4: k_ui_bg+2) );
+
+   ui_outline( box, 1, ui_colour(state? k_ui_fg+3: k_ui_bg+3), 0 );
+
+   /* TODO: replace this one day with our own function */
+   char buf[32];
+   snprintf( buf, sizeof(buf), format? format: "%.2f", *value );
+   ui_text( box, buf, 1, k_ui_align_middle_center, 0 );
+
+   return state;
+}
+
+void ui_slider( ui_rect inout_panel, const char *str_label, 
+                f32 min, f32 max, f32 *value, const char *format )
+{
+   ui_rect rect, label, box;
+   ui_standard_widget( inout_panel, rect, 1 );
+   ui_label( rect, str_label, k_ui_scale, 0, box );
+   _ui_slider( box, min, max, value, format );
+}
+
+/*
+ * Colour picker
+ * -----------------------------------------------------------------------------
+ */
+
+void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value )
+{
+   ui_rect widget, left, right;
+   ui_standard_widget( inout_panel, widget, 8 );
+   ui_split_ratio( widget, k_ui_axis_v, 0.5f, 8, left, right );
+
+   ui_rect sliders[4];
+   ui_split_ratio( right, k_ui_axis_h, 0.5f, 2, sliders[0], sliders[2] );
+   ui_split_ratio( sliders[0], k_ui_axis_h, 0.5f, 2, sliders[0], sliders[1] );
+   ui_split_ratio( sliders[2], k_ui_axis_h, 0.5f, 2, sliders[2], sliders[3] );
+
+   v4f hsv;
+   vg_rgb_hsv( value, hsv );
+   hsv[3] = value[3];
+
+   enum ui_button_state modified = 0x00;
+
+   for( u32 i=0; i<4; i ++ ){
+      modified |= _ui_slider( sliders[i], 0.0f, 1.0f, hsv+i, 
+                     (const char *[]){ "hue %.2f",
+                                       "sat %.2f",
+                                       "lum %.2f",
+                                       "alpha %.2f" }[i] );
+   }
+
+   ui_rect preview, square;
+   ui_split_ratio( left, k_ui_axis_v, 0.8f, 8, square, preview );
+
+   u32 state = ui_colourbutton( square, 0, 0, 0, 0 );
+   modified |= state;
+
+   enum ui_button_state 
+      mask_using = 
+         k_ui_button_holding_inside |
+         k_ui_button_holding_outside |
+         k_ui_button_click;
+   
+   if( state & mask_using ){
+      for( u32 i=0; i<2; i ++ ){
+         hsv[1+i] = vg_clampf( 
+                        (f32)(vg_ui.mouse[i] - square[i]) / (f32)(square[2+i]),
+                        0.0f, 1.0f );
+      }
+
+      hsv[2] = 1.0f-hsv[2];
+   }
+
+   if( modified & (k_ui_button_click|k_ui_button_holding_inside|
+                   k_ui_button_holding_outside) ){
+      vg_hsv_rgb( hsv, value );
+      value[3] = hsv[3];
+   }
+
+   ui_outline( square, 1, ui_colour( state? k_ui_fg+3: k_ui_bg+3 ), 0 );
+
+   /* preview colour */
+   v4f colour;
+   vg_hsv_rgb( hsv, colour );
+   colour[3] = 1.0f;
+   ui_fill( preview, v4f_u32_colour( colour ) );
+
+   /* Draw hsv shader thingy */
+   ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
+   ui_fill_rect( square, 0xffffffff, (ui_px[4]){ 0,256,256,0 } );
+   vg_ui.hue = hsv[0];
+   ui_flush( k_ui_shader_hsv, vg.window_x, vg.window_y );
+
+   ui_rect marker = { square[0] + hsv[1] * (f32)square[2] - 4, 
+                      square[1] + (1.0f-hsv[2]) * (f32)square[3] - 4,
+                      8, 8 },
+           lx     = { square[0],
+                      square[1] + (1.0f-hsv[2]) * (f32)square[3],
+                      square[2], 1 },
+           ly     = { square[0] + hsv[1] * (f32)square[2],
+                      square[1],
+                      1, square[3] };
+
+   ui_fill( marker, ui_colour( k_ui_fg ) );
+   ui_fill( lx, ui_colour( k_ui_fg ) );
+   ui_fill( ly, ui_colour( k_ui_fg ) );
+}
+
+/*
+ * Textbox chaos
+ * -----------------------------------------------------------------------------
+ */
+
+static void _ui_textbox_make_selection( int *start, int *end ){
+       *start = VG_MIN( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
+       *end   = VG_MAX( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
+}
+
+void _ui_textbox_move_cursor( int *cursor0, int *cursor1, 
+                              int dir, int snap_together )
+{
+       *cursor0 = VG_MAX( 0, vg_ui.textbox.cursor_user + dir );
+       *cursor0 = 
+      VG_MIN( 
+         VG_MIN( vg_ui.textbox.len-1, strlen( vg_ui.textbuf )), 
+      *cursor0 );
+
+       if( snap_together ) 
+               *cursor1 = *cursor0;
+}
+
+static int _ui_textbox_makeroom( int datastart, int length ){
+       int move_to = VG_MIN( datastart+length, vg_ui.textbox.len-1 );
+       int move_amount = strlen( vg_ui.textbuf )-datastart;
+       int move_end = VG_MIN( move_to+move_amount, vg_ui.textbox.len-1 );
+       move_amount = move_end-move_to;
+       
+       if( move_amount )
+               memmove( &vg_ui.textbuf[ move_to ], 
+               &vg_ui.textbuf[ datastart ], 
+               move_end-move_to );
+       
+       vg_ui.textbuf[ move_end ] = '\0';
+       
+       return VG_MIN( length, vg_ui.textbox.len-datastart-1 );
+}
+
+int _ui_textbox_delete_char( int direction )
+{
+       int start, end;
+       _ui_textbox_make_selection( &start, &end );
+       
+       /* There is no selection */
+       if( !(end-start) ){
+               if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_ui.textbuf ) );
+               else if( direction == -1 ) start = VG_MAX( start-1, 0 );
+       }
+       
+       /* Still no selction, no need to do anything */
+       if( !(end-start) ) 
+               return start;
+       
+       /* Copy the end->terminator 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; i<count; i++ ){
+      ui_rect button = {
+         bar[0] + ((f32)i*width),
+         bar[1],
+         width,
+         bar[3]-1
+      };
+
+      enum ui_scheme_colour colour = k_ui_bg+4;
+      if( i == cur_page ){
+         colour = k_ui_bg+2;
+         ui_outline( button, 1, ui_colour( k_ui_bg+5 ), 
+                     UI_TOP|UI_LEFT|UI_RIGHT );
+         button[3] ++;
+      }
+
+      if( ui_colourbutton_text( button, titles[i], 1, colour ) == 1 )
+         *page = i;
+   }
+}
+
+/* 
+ * Modal UI
+ * -----------------------------------------------------------------------------
+ */
+void ui_start_modal( const char *message, u32 options )
+{
+   vg_ui.focused_control_type = k_ui_control_modal;
+   vg_ui.modal.message = message;
+   vg_ui.modal.callbacks.close = NULL;
+   vg_ui.modal.options = options;
+   
+   u32 type = options & UI_MODAL_TYPE_BITS;
+   if(      type == UI_MODAL_OK )   vg_info( message );
+   else if( type == UI_MODAL_WARN ) vg_warn( message );
+   else if( type == UI_MODAL_GOOD ) vg_success( message );
+   else if( type == UI_MODAL_BAD )  vg_error( message );
+}
+
+/*
+ * Input handling
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * Handles binds
+ */
+void ui_proc_key( SDL_Keysym ev )
+{
+   if( vg_ui.focused_control_type != k_ui_control_textbox ){
+      return;
+   }
+
+   struct textbox_mapping{
+      u16 mod;
+      SDL_Keycode key;
+      
+      void (*handler)(void);
+   }
+   mappings[] =
+   {
+      { 0,              SDLK_LEFT,       _ui_textbox_left              },
+      { KMOD_SHIFT,     SDLK_LEFT,       _ui_textbox_left_select       },
+      { 0,              SDLK_RIGHT,      _ui_textbox_right             },
+      { KMOD_SHIFT,     SDLK_RIGHT,      _ui_textbox_right_select      },
+      { 0,              SDLK_DOWN,       _ui_textbox_down              },
+      { 0,              SDLK_UP,         _ui_textbox_up                },
+      { 0,              SDLK_BACKSPACE,  _ui_textbox_backspace         },
+      { KMOD_SHIFT,     SDLK_BACKSPACE,  _ui_textbox_backspace         },
+      { KMOD_CTRL,      SDLK_BACKSPACE,  _ui_textbox_backspace         },
+      { 0,              SDLK_DELETE,     _ui_textbox_delete            },
+      { 0,              SDLK_HOME,       _ui_textbox_home              },
+      { KMOD_SHIFT,     SDLK_HOME,       _ui_textbox_home_select       },
+      { 0,              SDLK_END,        _ui_textbox_end               },
+      { KMOD_SHIFT,     SDLK_END,        _ui_textbox_end_select        },
+      { KMOD_CTRL,      SDLK_a,          _ui_textbox_select_all        },
+      { KMOD_CTRL,      SDLK_c,          _ui_textbox_to_clipboard      },
+      { KMOD_CTRL,      SDLK_x,          _ui_textbox_cut               },
+      { KMOD_CTRL,      SDLK_v,          _ui_textbox_clipboard_paste   },
+      { 0,              SDLK_RETURN,     _ui_textbox_enter             },
+      { 0,              SDLK_ESCAPE,     ui_defocus_all                },
+   };
+
+   SDL_Keymod mod = 0;
+
+   if( ev.mod & KMOD_SHIFT )
+      mod |= KMOD_SHIFT;
+
+   if( ev.mod & KMOD_CTRL )
+      mod |= KMOD_CTRL;
+
+   if( ev.mod & KMOD_ALT )
+      mod |= KMOD_ALT;
+
+   for( int i=0; i<vg_list_size( mappings ); i++ ){
+      struct textbox_mapping *mapping = &mappings[i];
+
+      if( mapping->key == 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<vg_list_size(vg_ui.scheme); i++ ){
+      int which = (i/8)%2;
+
+      ui_split( col[which], k_ui_axis_h, 24, 0, swatch, col[which] );
+      ui_fill( swatch, ui_colour(i) );
+
+      if( names[i] )
+         ui_text(swatch, names[i], 1, k_ui_align_middle_left, ui_colourcont(i));
+   }
+}
index 4604327dd25197767b935ac0c6bf1485c7fd3887..4e725a824c37c62cc9e6448f1c0e349f4796534d 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
+/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */
 
 /*
  *  Principles:
  *       point.
  */
 
-#ifndef VG_IMGUI_H
-#define VG_IMGUI_H
+#pragma once
 
-#define VG_GAME
-#include "vg/vg.h"
-#include "vg/vg_tex.h"
-#include "vg/vg_shader.h"
+#include "vg_engine.h"
+#include "vg_tex.h"
+#include "vg_shader.h"
 
 typedef i16                            ui_px;
 typedef ui_px                          ui_rect[4];
@@ -73,9 +71,15 @@ enum ui_scheme_colour{
    k_ui_brighter = 8
 };
 
-static ui_px k_ui_widget_height = 28,
-             k_ui_scale = 1,
-             k_ui_padding = 8;
+enum ui_shader {
+   k_ui_shader_colour,
+   k_ui_shader_image,
+   k_ui_shader_hsv,
+};
+
+extern ui_px k_ui_widget_height,
+             k_ui_scale,
+             k_ui_padding;
 
 typedef u32 ui_scheme[8*4];
 
@@ -92,26 +96,7 @@ struct ui_font {
 };
 typedef struct ui_font ui_font;
 
-static const 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
-};
+extern ui_font vg_ui_font_small, vg_ui_font_big;
 
 #define UI_RGB( STDHEX )          0xff000000       |\
                          ((STDHEX&0x000000ff)<<16) |\
@@ -137,7 +122,8 @@ vg_ui_font_big = {
 #define UI_BOTTOM 0x4
 #define UI_RIGHT 0x8
 
-struct{
+struct vg_imgui
+{
    struct ui_vert *vertex_buffer;
    u16            *indice_buffer;
        u32 max_verts, max_indices, 
@@ -228,897 +214,7 @@ struct{
 
    f32 hue; /* lol this sucks */
 } 
-static 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 );"
-       "}"
-   }
-};
-
-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_list_size(mesh); i++ ){
-      indices[i] = start+mesh[i];
-   }
-
-       vg_ui.cur_indice += 6;
-       vg_ui.cur_vert += 4;
-}
-
-static void ui_fill( ui_rect rect, u32 colour ){
-   ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
-}
-
-static void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ){
-   /* this if far from ideal but stops us from crashing */
-   if( (vg_ui.cur_vert + 8 > 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<vg_list_size(mesh)/6; i++ ){
-      if( (0x1<<i) & mask ){
-         for( u32 j=0; j<6; j++ )
-            indices[c ++] = start+mesh[i*6+j];
-      }
-   }
-       
-       vg_ui.cur_indice += c;
-       vg_ui.cur_vert += 8;
-}
-
-static void ui_split( ui_rect rect, 
-                       enum ui_axis other, ui_px width, ui_px gap,
-                       ui_rect l, ui_rect r ){
-   enum ui_axis dir = other ^ 0x1;
-
-   if( width < 0 ) width = rect[ 2+dir ] + width;
-
-   ui_rect temp;
-   rect_copy( rect, temp );
-
-   l[ dir ]     = temp[ dir ];
-   r[ dir ]     = temp[ dir ] + width + (gap/2);
-   l[ other ]   = temp[ other ];
-   r[ other ]   = temp[ other ];
-   l[ 2+dir ]   = width                 - (gap/2);
-   r[ 2+dir ]   = temp[ 2+dir ] - width - (gap/2);
-   l[ 2+other ] = temp[ 2+other ];
-   r[ 2+other ] = temp[ 2+other ];
-}
-
-static ui_px ui_text_line_width( const char *str );
-
-static void ui_rect_center( ui_rect parent, ui_rect rect ){
-   rect[0] = parent[0] + (parent[2]-rect[2])/2;
-   rect[1] = parent[1] + (parent[3]-rect[3])/2;
-}
-
-static void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ){
-   i32 rp = (i32)rect[2] * (i32)size[1],
-       rc = (i32)size[0] * (i32)rect[3];
-
-   enum ui_axis dir, other;
-   if( rc > 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 a<b?a:b; }
-static ui_px ui_max( ui_px a, ui_px b ){ return a>b?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; i<len; i ++ ){
-      if( *value == options[i].value ){
-         display = options[i].alias;
-         valid = 1;
-         break;
-      }
-   }
-
-   if( ui_button_text( box, display, k_ui_scale ) == 1 ){
-      vg_ui.focused_control_type = k_ui_control_enum;
-      vg_ui.ptr_enum = value;
-      vg_ui._enum.option_count = len;
-      vg_ui._enum.options = options;
-      rect_copy( box, vg_ui._enum.rect );
-   }
-
-   if( !valid )
-      ui_outline( box, 1, ui_colour(k_ui_red), 0 );
-}
-
-static void ui_enum_post(void){
-   ui_rect drawer;
-   rect_copy( vg_ui._enum.rect, drawer );
-   drawer[3] *= vg_ui._enum.option_count;
-
-   int close = 0;
-   int clickany= ui_click_up(UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE),
-       hover   = ui_inside_rect( drawer, vg_ui.mouse );
-
-   if( clickany && !hover ){
-      return;
-   }
-
-   /* HACK */
-   vg_ui.focused_control_type = k_ui_control_none;
-   i32 *value = vg_ui.ptr_enum;
-
-   for( u32 i=0; i<vg_ui._enum.option_count; i++ ){
-      ui_rect button;
-      ui_split( drawer, k_ui_axis_h, vg_ui._enum.rect[3], 0, button,drawer );
-      
-      enum ui_scheme_colour colour = k_ui_bg+3;
-      if( vg_ui._enum.options[i].value == *value ) 
-         colour = k_ui_orange;
-
-      if( ui_colourbutton_text( button, vg_ui._enum.options[i].alias, 
-                                k_ui_scale, colour ) == 1 ){
-         *value = vg_ui._enum.options[i].value;
-         close = 1;
-      }
-   }
-
-   /* HACK */
-   vg_ui.focused_control_type = k_ui_control_enum;
-
-   if( !close )
-      vg_ui.focused_control_hit = 1;
-}
-
-/*
- * Slider
- * -----------------------------------------------------------------------------
- */
-
-static enum ui_button_state _ui_slider( ui_rect box, 
-                        f32 min, f32 max, f32 *value, const char *format ){
-   f32 t;
-
-   enum ui_button_state 
-      mask_using = 
-         k_ui_button_holding_inside |
-         k_ui_button_holding_outside |
-         k_ui_button_click,
-      mask_brighter =
-         mask_using | k_ui_button_hover,
-      state = ui_colourbutton( box, k_ui_bg, k_ui_bg+2, k_ui_bg+3, 1 );
-
-   if( state & mask_using ){
-      t = vg_clampf( (f32)(vg_ui.mouse[0] - box[0]) / (f32)( box[2] ),
-                     0.0f, 1.0f );
-      *value = vg_lerpf( min, max, t );
-   }
-   else
-      t = vg_clampf( (*value - min) / (max-min), 0.0f, 1.0f );
-   
-   ui_rect line     = { box[0], box[1], t * (f32)box[2], box[3] };
-   ui_fill( line, ui_colour(state&mask_brighter? k_ui_bg+4: k_ui_bg+2) );
-
-   ui_outline( box, 1, ui_colour(state? k_ui_fg+3: k_ui_bg+3), 0 );
-
-   /* TODO: replace this one day with our own function */
-   char buf[32];
-   snprintf( buf, sizeof(buf), format? format: "%.2f", *value );
-   ui_text( box, buf, 1, k_ui_align_middle_center, 0 );
-
-   return state;
-}
-
-static void ui_slider( ui_rect inout_panel, const char *str_label, 
-                       f32 min, f32 max, f32 *value, const char *format ){
-   ui_rect rect, label, box;
-   ui_standard_widget( inout_panel, rect, 1 );
-   ui_label( rect, str_label, k_ui_scale, 0, box );
-   _ui_slider( box, min, max, value, format );
-}
-
-/*
- * Colour picker
- * -----------------------------------------------------------------------------
- */
-
-static void ui_colourpicker( ui_rect inout_panel, const char *str_label,
-                             v4f value ){
-   ui_rect widget, left, right;
-   ui_standard_widget( inout_panel, widget, 8 );
-   ui_split_ratio( widget, k_ui_axis_v, 0.5f, 8, left, right );
-
-   ui_rect sliders[4];
-   ui_split_ratio( right, k_ui_axis_h, 0.5f, 2, sliders[0], sliders[2] );
-   ui_split_ratio( sliders[0], k_ui_axis_h, 0.5f, 2, sliders[0], sliders[1] );
-   ui_split_ratio( sliders[2], k_ui_axis_h, 0.5f, 2, sliders[2], sliders[3] );
-
-   v4f hsv;
-   vg_rgb_hsv( value, hsv );
-   hsv[3] = value[3];
-
-   enum ui_button_state modified = 0x00;
-
-   for( u32 i=0; i<4; i ++ ){
-      modified |= _ui_slider( sliders[i], 0.0f, 1.0f, hsv+i, 
-                     (const char *[]){ "hue %.2f",
-                                       "sat %.2f",
-                                       "lum %.2f",
-                                       "alpha %.2f" }[i] );
-   }
-
-   ui_rect preview, square;
-   ui_split_ratio( left, k_ui_axis_v, 0.8f, 8, square, preview );
-
-   u32 state = ui_colourbutton( square, 0, 0, 0, 0 );
-   modified |= state;
-
-   enum ui_button_state 
-      mask_using = 
-         k_ui_button_holding_inside |
-         k_ui_button_holding_outside |
-         k_ui_button_click;
-   
-   if( state & mask_using ){
-      for( u32 i=0; i<2; i ++ ){
-         hsv[1+i] = vg_clampf( 
-                        (f32)(vg_ui.mouse[i] - square[i]) / (f32)(square[2+i]),
-                        0.0f, 1.0f );
-      }
-
-      hsv[2] = 1.0f-hsv[2];
-   }
-
-   if( modified & (k_ui_button_click|k_ui_button_holding_inside|
-                   k_ui_button_holding_outside) ){
-      vg_hsv_rgb( hsv, value );
-      value[3] = hsv[3];
-   }
-
-   ui_outline( square, 1, ui_colour( state? k_ui_fg+3: k_ui_bg+3 ), 0 );
-
-   /* preview colour */
-   v4f colour;
-   vg_hsv_rgb( hsv, colour );
-   colour[3] = 1.0f;
-   ui_fill( preview, v4f_u32_colour( colour ) );
-
-   /* Draw hsv shader thingy */
-   ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
-   ui_fill_rect( square, 0xffffffff, (ui_px[4]){ 0,256,256,0 } );
-   vg_ui.hue = hsv[0];
-   ui_flush( k_ui_shader_hsv, vg.window_x, vg.window_y );
-
-   ui_rect marker = { square[0] + hsv[1] * (f32)square[2] - 4, 
-                      square[1] + (1.0f-hsv[2]) * (f32)square[3] - 4,
-                      8, 8 },
-           lx     = { square[0],
-                      square[1] + (1.0f-hsv[2]) * (f32)square[3],
-                      square[2], 1 },
-           ly     = { square[0] + hsv[1] * (f32)square[2],
-                      square[1],
-                      1, square[3] };
-
-   ui_fill( marker, ui_colour( k_ui_fg ) );
-   ui_fill( lx, ui_colour( k_ui_fg ) );
-   ui_fill( ly, ui_colour( k_ui_fg ) );
-}
-
-/*
- * Textbox chaos
- * -----------------------------------------------------------------------------
- */
-
-static void _ui_textbox_make_selection( int *start, int *end ){
-       *start = VG_MIN( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
-       *end   = VG_MAX( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
-}
-
-static void _ui_textbox_move_cursor( int *cursor0, int *cursor1, 
-                                     int dir, int snap_together ){
-       *cursor0 = VG_MAX( 0, vg_ui.textbox.cursor_user + dir );
-       *cursor0 = 
-      VG_MIN( 
-         VG_MIN( vg_ui.textbox.len-1, strlen( vg_ui.textbuf )), 
-      *cursor0 );
-
-       if( snap_together ) 
-               *cursor1 = *cursor0;
-}
-
-static int _ui_textbox_makeroom( int datastart, int length ){
-       int move_to = VG_MIN( datastart+length, vg_ui.textbox.len-1 );
-       int move_amount = strlen( vg_ui.textbuf )-datastart;
-       int move_end = VG_MIN( move_to+move_amount, vg_ui.textbox.len-1 );
-       move_amount = move_end-move_to;
-       
-       if( move_amount )
-               memmove( &vg_ui.textbuf[ move_to ], 
-               &vg_ui.textbuf[ datastart ], 
-               move_end-move_to );
-       
-       vg_ui.textbuf[ move_end ] = '\0';
-       
-       return VG_MIN( length, vg_ui.textbox.len-datastart-1 );
-}
-
-static int _ui_textbox_delete_char( int direction ){
-       int start, end;
-       _ui_textbox_make_selection( &start, &end );
-       
-       /* There is no selection */
-       if( !(end-start) ){
-               if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_ui.textbuf ) );
-               else if( direction == -1 ) start = VG_MAX( start-1, 0 );
-       }
-       
-       /* Still no selction, no need to do anything */
-       if( !(end-start) ) 
-               return start;
-       
-       /* Copy the end->terminator 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; i<count; i++ ){
-      ui_rect button = {
-         bar[0] + ((f32)i*width),
-         bar[1],
-         width,
-         bar[3]-1
-      };
-
-      enum ui_scheme_colour colour = k_ui_bg+4;
-      if( i == cur_page ){
-         colour = k_ui_bg+2;
-         ui_outline( button, 1, ui_colour( k_ui_bg+5 ), 
-                     UI_TOP|UI_LEFT|UI_RIGHT );
-         button[3] ++;
-      }
-
-      if( ui_colourbutton_text( button, titles[i], 1, colour ) == 1 )
-         *page = i;
-   }
-}
-
-/* 
- * Modal UI
- * -----------------------------------------------------------------------------
- */
-static void ui_start_modal( const char *message, u32 options ){
-   vg_ui.focused_control_type = k_ui_control_modal;
-   vg_ui.modal.message = message;
-   vg_ui.modal.callbacks.close = NULL;
-   vg_ui.modal.options = options;
-   
-   u32 type = options & UI_MODAL_TYPE_BITS;
-   if(      type == UI_MODAL_OK )   vg_info( message );
-   else if( type == UI_MODAL_WARN ) vg_warn( message );
-   else if( type == UI_MODAL_GOOD ) vg_success( message );
-   else if( type == UI_MODAL_BAD )  vg_error( message );
-}
-
-/*
- * Input handling
- * -----------------------------------------------------------------------------
- */
-
-/*
- * Handles binds
- */
-static void _ui_proc_key( SDL_Keysym ev ){
-   if( vg_ui.focused_control_type != k_ui_control_textbox ){
-      return;
-   }
-
-   struct textbox_mapping{
-      u16 mod;
-      SDL_Keycode key;
-      
-      void (*handler)(void);
-   }
-   mappings[] =
-   {
-      { 0,              SDLK_LEFT,       _ui_textbox_left              },
-      { KMOD_SHIFT,     SDLK_LEFT,       _ui_textbox_left_select       },
-      { 0,              SDLK_RIGHT,      _ui_textbox_right             },
-      { KMOD_SHIFT,     SDLK_RIGHT,      _ui_textbox_right_select      },
-      { 0,              SDLK_DOWN,       _ui_textbox_down              },
-      { 0,              SDLK_UP,         _ui_textbox_up                },
-      { 0,              SDLK_BACKSPACE,  _ui_textbox_backspace         },
-      { KMOD_SHIFT,     SDLK_BACKSPACE,  _ui_textbox_backspace         },
-      { KMOD_CTRL,      SDLK_BACKSPACE,  _ui_textbox_backspace         },
-      { 0,              SDLK_DELETE,     _ui_textbox_delete            },
-      { 0,              SDLK_HOME,       _ui_textbox_home              },
-      { KMOD_SHIFT,     SDLK_HOME,       _ui_textbox_home_select       },
-      { 0,              SDLK_END,        _ui_textbox_end               },
-      { KMOD_SHIFT,     SDLK_END,        _ui_textbox_end_select        },
-      { KMOD_CTRL,      SDLK_a,          _ui_textbox_select_all        },
-      { KMOD_CTRL,      SDLK_c,          _ui_textbox_to_clipboard      },
-      { KMOD_CTRL,      SDLK_x,          _ui_textbox_cut               },
-      { KMOD_CTRL,      SDLK_v,          _ui_textbox_clipboard_paste   },
-      { 0,              SDLK_RETURN,     _ui_textbox_enter             },
-      { 0,              SDLK_ESCAPE,     ui_defocus_all                },
-   };
-
-   SDL_Keymod mod = 0;
-
-   if( ev.mod & KMOD_SHIFT )
-      mod |= KMOD_SHIFT;
-
-   if( ev.mod & KMOD_CTRL )
-      mod |= KMOD_CTRL;
-
-   if( ev.mod & KMOD_ALT )
-      mod |= KMOD_ALT;
-
-   for( int i=0; i<vg_list_size( mappings ); i++ ){
-      struct textbox_mapping *mapping = &mappings[i];
-
-      if( mapping->key == 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; i<vg_list_size(vg_ui.scheme); i++ ){
-      int which = (i/8)%2;
-
-      ui_split( col[which], k_ui_axis_h, 24, 0, swatch, col[which] );
-      ui_fill( swatch, ui_colour(i) );
-
-      if( names[i] )
-         ui_text(swatch, names[i], 1, k_ui_align_middle_left, ui_colourcont(i));
-   }
-}
-
-#endif /* VG_IMGUI_H */
+      enum ui_scheme_colour colour );
+enum ui_button_state ui_button_text( ui_rect rect, 
+                                     const char *string, ui_px scale );
+enum ui_button_state ui_button( ui_rect inout_panel, const char *string );
+void ui_postrender(void);
+
+int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data );
+void ui_enum( ui_rect inout_panel, const char *str_label, 
+              struct ui_enum_opt *options, u32 len, i32 *value );
+void ui_slider( ui_rect inout_panel, const char *str_label, 
+                f32 min, f32 max, f32 *value, const char *format );
+void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value );
+int ui_textbox( ui_rect inout_panel, const char *label,
+                char *buf, u32 len, u32 lines, u32 flags,
+                struct ui_textbox_callbacks *callbacks );
+void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel,
+              const char **titles, u32 count, i32 *page );
+void ui_start_modal( const char *message, u32 options );
+void ui_proc_key( SDL_Keysym ev );
+void ui_proc_utf8( const char *text );
+void ui_dev_colourview(void);
+
+void _ui_textbox_move_cursor( int *cursor0, int *cursor1, 
+                              int dir, int snap_together );
+int _ui_textbox_delete_char( int direction );
+void _ui_textbox_put_char( char c );
+void _ui_textbox_up(void);
+void _ui_textbox_left(void);
+void _ui_textbox_left_select(void);
+void _ui_textbox_down(void);
+void _ui_textbox_right_select(void);
+void _ui_textbox_right(void);
+void _ui_textbox_backspace(void);
+void _ui_textbox_delete(void);
+void _ui_textbox_home_select(void);
+void _ui_textbox_home(void);
+void _ui_textbox_end_select(void);
+void _ui_textbox_end(void);
+void _ui_textbox_select_all(void);
+void _ui_textbox_cut(void);
+void _ui_textbox_enter(void);
diff --git a/vg_input.c b/vg_input.c
new file mode 100644 (file)
index 0000000..3c138d0
--- /dev/null
@@ -0,0 +1,584 @@
+/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */
+
+#include "vg_input.h"
+#include "vg_loader.h"
+#include "vg_engine.h"
+#include "vg_async.h"
+#include "vg_string.h"
+
+f32 controller_deadzone = 0.05f;
+
+struct vg_input vg_input = {
+   .active_controller_index = -2 
+};
+
+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; j<VG_MAX_CONTROLLERS; j++ ){
+      struct vg_controller *esta = &vg_input.controllers[j];
+
+      if( esta->handle ){
+         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; i<VG_MAX_CONTROLLERS; i++ ){
+         struct vg_controller *controller = &vg_input.controllers[i];
+
+         if( controller->handle ){
+            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; i<VG_MAX_CONTROLLERS; i++ ){
+         struct vg_controller *esta = &vg_input.controllers[i];
+
+         if( ev->caxis.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; i<VG_MAX_CONTROLLERS; i++ ){
+            if( vg_input.controllers[i].instance_id == ev->cbutton.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; i<VG_MAX_CONTROLLERS; i++ ){
+         struct vg_controller *esta = &vg_input.controllers[i];
+
+         if( ev->cbutton.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; i<count; i++ ){
+         if( vg_input.sdl_keys[i] ){
+            vg_input.display_input_method = k_input_method_kbm;
+            vg_info( "display_input: k_input_method_kbm (keyboard %d)\n", i );
+            break;
+         }
+      }
+
+      /* check for giving mouse priority */
+      if( vg_input.sdl_mouse & 
+            (SDL_BUTTON(SDL_BUTTON_LEFT)|SDL_BUTTON(SDL_BUTTON_RIGHT)|
+             SDL_BUTTON(SDL_BUTTON_MIDDLE)) )
+      {
+         vg_input.display_input_method = k_input_method_kbm;
+         vg_info( "display_input: k_input_method_kbm (mouse)\n" );
+      }
+   }
+}
+
+void async_vg_input_init( void *payload, u32 size )
+{
+   vg_info( "Checking for controllers\n" );
+   SDL_GameControllerAddMappingsFromFile( "gamecontrollerdb.txt" );
+   
+   int joy_count = SDL_NumJoysticks();
+   for( int i=0; i<joy_count; i++ ) {
+      const char *name = SDL_JoystickNameForIndex( i );
+      int is_controller = SDL_IsGameController(i);
+
+      vg_info( "%d: %s [controller: %d]\n", i, name, is_controller );
+
+      if( is_controller ){
+         vg_open_gamecontroller( i );
+      }
+   }
+}
+
+void vg_input_init(void)
+{
+   VG_VAR_F32( controller_deadzone, flags=VG_VAR_PERSISTENT );
+   vg_async_call( async_vg_input_init, NULL, 0 );
+}
+
+void vg_input_free(void)
+{
+   for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+      struct vg_controller *controller = &vg_input.controllers[i];
+
+      if( controller->handle ){
+         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;
+}
index 0122c92e5043f26ccbb9141fb1ad688f3dd366f4..a203123e4b783b5a0e1835acfdb7b2d981e3772b 100644 (file)
@@ -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; j<VG_MAX_CONTROLLERS; j++ ){
-      struct vg_controller *esta = &vg_input.controllers[j];
-
-      if( esta->handle ){
-         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; i<VG_MAX_CONTROLLERS; i++ ){
-         struct vg_controller *controller = &vg_input.controllers[i];
-
-         if( controller->handle ){
-            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; i<VG_MAX_CONTROLLERS; i++ ){
-         struct vg_controller *esta = &vg_input.controllers[i];
-
-         if( ev->caxis.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; i<VG_MAX_CONTROLLERS; i++ ){
-            if( vg_input.controllers[i].instance_id == ev->cbutton.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; i<VG_MAX_CONTROLLERS; i++ ){
-         struct vg_controller *esta = &vg_input.controllers[i];
-
-         if( ev->cbutton.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; i<count; i++ ){
-         if( vg_input.sdl_keys[i] ){
-            vg_input.display_input_method = k_input_method_kbm;
-            vg_info( "display_input: k_input_method_kbm (keyboard %d)\n", i );
-            break;
-         }
-      }
-
-      /* check for giving mouse priority */
-      if( vg_input.sdl_mouse & 
-            (SDL_BUTTON(SDL_BUTTON_LEFT)|SDL_BUTTON(SDL_BUTTON_RIGHT)|
-             SDL_BUTTON(SDL_BUTTON_MIDDLE)) )
-      {
-         vg_input.display_input_method = k_input_method_kbm;
-         vg_info( "display_input: k_input_method_kbm (mouse)\n" );
-      }
-   }
-}
-
-static void async_vg_input_init( void *payload, u32 size )
-{
-   VG_VAR_F32( controller_deadzone, flags=VG_VAR_PERSISTENT );
-
-   vg_info( "Checking for controllers\n" );
-   SDL_GameControllerAddMappingsFromFile( "gamecontrollerdb.txt" );
-   
-   int joy_count = SDL_NumJoysticks();
-   for( int i=0; i<joy_count; i++ ) {
-      const char *name = SDL_JoystickNameForIndex( i );
-      int is_controller = SDL_IsGameController(i);
-
-      vg_info( "%d: %s [controller: %d]\n", i, name, is_controller );
-
-      if( is_controller ){
-         vg_open_gamecontroller( i );
-      }
-   }
-}
-
-static void vg_input_init(void)
-{
-   vg_async_call( async_vg_input_init, NULL, 0 );
-}
-
-static void vg_input_free(void)
-{
-   for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
-      struct vg_controller *controller = &vg_input.controllers[i];
-
-      if( controller->handle ){
-         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 (file)
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 <string.h>
+#include <errno.h>
+
+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 e39c15fa5975b6103e791cb56b41991016c252b6..fefb6bfa1af487c141896a1f1723790f2c69cfaa 100644 (file)
--- 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 <dirent.h>
@@ -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 (file)
index 0000000..27694fc
--- /dev/null
@@ -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<<j)? box[1][j]: box[0][j];
+      }
+   }
+}
+
+void vg_line_mesh( v3f verts[], u32 indices[][2], u32 indice_count,u32 colour ){
+   if( !vg_lines.enabled ) return;
+
+   for( u32 i=0; i<indice_count; i++ ){
+      vg_line( verts[indices[i][0]], verts[indices[i][1]], colour );
+   }
+}
+
+void vg_line_boxf( boxf box, u32 colour )
+{
+   if( !vg_lines.enabled ) return;
+
+   v3f verts[8];
+   vg_line_box_verts( box, verts );
+   u32 indices[][2] = {{0,1},{1,3},{3,2},{2,0},
+                       {4,5},{5,7},{7,6},{6,4},
+                       {4,0},{5,1},{6,2},{7,3}};
+
+   vg_line_mesh( verts, indices, vg_list_size(indices), colour );
+}
+
+void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour )
+{
+   if( !vg_lines.enabled ) return;
+
+   v3f verts[8];
+   vg_line_box_verts( box, verts );
+
+   for( u32 i=0; i<8; i++ ){
+      m4x3_mulv( m, verts[i], verts[i] );
+   }
+
+   u32 indices[][2] = {{0,1},{1,3},{3,2},{2,0},
+                       {4,5},{5,7},{7,6},{6,4},
+                       {4,0},{5,1},{6,2},{7,3}};
+
+   vg_line_mesh( verts, indices, vg_list_size(indices), colour );
+}
+
+void vg_line_cross(v3f pos,u32 colour, float scale)
+{
+   if( !vg_lines.enabled ) return;
+
+   v3f p0, p1;
+   v3_add( (v3f){ scale,0.0f,0.0f}, pos, p0 );
+   v3_add( (v3f){-scale,0.0f,0.0f}, pos, p1 );
+   vg_line( p0, p1, colour );
+   v3_add( (v3f){0.0f, scale,0.0f}, pos, p0 );
+   v3_add( (v3f){0.0f,-scale,0.0f}, pos, p1 );
+   vg_line( p0, p1, colour );
+   v3_add( (v3f){0.0f,0.0f, scale}, pos, p0 );
+   v3_add( (v3f){0.0f,0.0f,-scale}, pos, p1 );
+   vg_line( p0, p1, colour );
+}
+
+void vg_line_point( v3f pt, float size, u32 colour )
+{
+   if( !vg_lines.enabled ) return;
+
+   boxf box =
+   {
+      { pt[0]-size, pt[1]-size, pt[2]-size },
+      { pt[0]+size, pt[1]+size, pt[2]+size }
+   };
+
+   vg_line_boxf( box, colour );
+}
+
+
+void vg_line_sphere( m4x3f m, float radius, u32 colour )
+{
+   if( !vg_lines.enabled ) return;
+
+   v3f ly = { 0.0f, 0.0f, radius },
+       lx = { 0.0f, radius, 0.0f },
+       lz = { 0.0f, 0.0f, radius };
+   
+   for( int i=0; i<16; i++ ){
+      float t = ((float)(i+1) * (1.0f/16.0f)) * VG_PIf * 2.0f,
+            s = sinf(t),
+            c = cosf(t);
+
+      v3f py = { s*radius, 0.0f, c*radius },
+          px = { s*radius, c*radius, 0.0f },
+          pz = { 0.0f, s*radius, c*radius };
+
+      v3f p0, p1, p2, p3, p4, p5;
+      m4x3_mulv( m, py, p0 );
+      m4x3_mulv( m, ly, p1 );
+      m4x3_mulv( m, px, p2 );
+      m4x3_mulv( m, lx, p3 );
+      m4x3_mulv( m, pz, p4 );
+      m4x3_mulv( m, lz, p5 );
+
+      vg_line( p0, p1, colour == 0x00? 0xff00ff00: colour );
+      vg_line( p2, p3, colour == 0x00? 0xff0000ff: colour );
+      vg_line( p4, p5, colour == 0x00? 0xffff0000: colour );
+
+      v3_copy( py, ly );
+      v3_copy( px, lx );
+      v3_copy( pz, lz );
+   }
+}
+
+void vg_line_capsule( m4x3f m, float radius, float h, u32 colour )
+{
+   if( !vg_lines.enabled ) return;
+   
+   v3f ly = { 0.0f, 0.0f, radius },
+       lx = { 0.0f, radius, 0.0f },
+       lz = { 0.0f, 0.0f, radius };
+
+   float s0 = sinf(0.0f)*radius,
+         c0 = cosf(0.0f)*radius;
+
+   v3f p0, p1, up, right, forward;
+   m3x3_mulv( m, (v3f){0.0f,1.0f,0.0f}, up );
+   m3x3_mulv( m, (v3f){1.0f,0.0f,0.0f}, right );
+   m3x3_mulv( m, (v3f){0.0f,0.0f,-1.0f}, forward );
+   v3_muladds( m[3], up, -h*0.5f+radius, p0 );
+   v3_muladds( m[3], up,  h*0.5f-radius, p1 );
+
+   v3f a0, a1, b0, b1;
+   v3_muladds( p0, right, radius, a0 );
+   v3_muladds( p1, right, radius, a1 );
+   v3_muladds( p0, forward, radius, b0 );
+   v3_muladds( p1, forward, radius, b1 );
+   vg_line( a0, a1, colour );
+   vg_line( b0, b1, colour );
+
+   v3_muladds( p0, right, -radius, a0 );
+   v3_muladds( p1, right, -radius, a1 );
+   v3_muladds( p0, forward, -radius, b0 );
+   v3_muladds( p1, forward, -radius, b1 );
+   vg_line( a0, a1, colour );
+   vg_line( b0, b1, colour );
+   
+   for( int i=0; i<16; i++ ){
+      float t = ((float)(i+1) * (1.0f/16.0f)) * VG_PIf * 2.0f,
+            s1 = sinf(t)*radius,
+            c1 = cosf(t)*radius;
+
+      v3f e0 = { s0, 0.0f, c0 },
+          e1 = { s1, 0.0f, c1 },
+          e2 = { s0, c0, 0.0f },
+          e3 = { s1, c1, 0.0f },
+          e4 = { 0.0f, c0, s0 },
+          e5 = { 0.0f, c1, s1 };
+
+      m3x3_mulv( m, e0, e0 );
+      m3x3_mulv( m, e1, e1 );
+      m3x3_mulv( m, e2, e2 );
+      m3x3_mulv( m, e3, e3 );
+      m3x3_mulv( m, e4, e4 );
+      m3x3_mulv( m, e5, e5 );
+
+      v3_add( p0, e0, a0 );
+      v3_add( p0, e1, a1 );
+      v3_add( p1, e0, b0 );
+      v3_add( p1, e1, b1 );
+
+      vg_line( a0, a1, colour );
+      vg_line( b0, b1, colour );
+
+      if( c0 < 0.0f ){
+         v3_add( p0, e2, a0 );
+         v3_add( p0, e3, a1 );
+         v3_add( p0, e4, b0 );
+         v3_add( p0, e5, b1 );
+      }
+      else{
+         v3_add( p1, e2, a0 );
+         v3_add( p1, e3, a1 );
+         v3_add( p1, e4, b0 );
+         v3_add( p1, e5, b1 );
+      }
+
+      vg_line( a0, a1, colour );
+      vg_line( b0, b1, colour );
+
+      s0 = s1;
+      c0 = c1;
+   }
+}
index 8fff226106e7bc2a007611636226a339910ac294..dbb37c5ba5d408c97f78fcfbac984fe06b29ad82 100644 (file)
@@ -1,14 +1,7 @@
-/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
+/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */
 
-#ifndef VG_LINES_H
-#define VG_LINES_H
-
-#define VG_GAME
-#include "vg/vg.h"
-
-/*
- * FIXME: The line buffer sometimes overflows. Low priority
- */
+#pragma once
+#include "vg_platform.h"
 
 typedef v3f line_co;
 
@@ -23,42 +16,8 @@ typedef v3f line_co;
 #define VG__CYAN  0xffffff00
 #define VG__NONE  0x00000000
 
-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;"
-       "}"
-   }
-};
-
-struct{
+struct vg_lines
+{
    u32 enabled, 
        render;
        
@@ -70,302 +29,18 @@ struct{
 
        GLuint vao, vbo;
 }
-static vg_lines;
-
-#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();
-}
-
-static 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 );
-}
-
-static 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 );
-}
-
-static 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;
-}
-
-static void vg_line( line_co from, line_co to, u32 colour ){
-   if( !vg_lines.enabled ) return;
-
-       vg_line2( from, to, colour, colour );
-}
-
-static 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 );
-}
-
-static 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<<j)? box[1][j]: box[0][j];
-      }
-   }
-}
-
-static void vg_line_mesh( v3f verts[], u32 indices[][2], u32 indice_count,
-                             u32 colour ){
-   if( !vg_lines.enabled ) return;
-
-   for( u32 i=0; i<indice_count; i++ ){
-      vg_line( verts[indices[i][0]], verts[indices[i][1]], colour );
-   }
-}
-
-static void vg_line_boxf( boxf box, u32 colour ){
-   if( !vg_lines.enabled ) return;
-
-   v3f verts[8];
-   vg_line_box_verts( box, verts );
-   u32 indices[][2] = {{0,1},{1,3},{3,2},{2,0},
-                       {4,5},{5,7},{7,6},{6,4},
-                       {4,0},{5,1},{6,2},{7,3}};
-
-   vg_line_mesh( verts, indices, vg_list_size(indices), colour );
-}
-
-static void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour ){
-   if( !vg_lines.enabled ) return;
-
-   v3f verts[8];
-   vg_line_box_verts( box, verts );
-
-   for( u32 i=0; i<8; i++ ){
-      m4x3_mulv( m, verts[i], verts[i] );
-   }
-
-   u32 indices[][2] = {{0,1},{1,3},{3,2},{2,0},
-                       {4,5},{5,7},{7,6},{6,4},
-                       {4,0},{5,1},{6,2},{7,3}};
-
-   vg_line_mesh( verts, indices, vg_list_size(indices), colour );
-}
-
-static void vg_line_cross(v3f pos,u32 colour, float scale){
-   if( !vg_lines.enabled ) return;
-
-   v3f p0, p1;
-   v3_add( (v3f){ scale,0.0f,0.0f}, pos, p0 );
-   v3_add( (v3f){-scale,0.0f,0.0f}, pos, p1 );
-   vg_line( p0, p1, colour );
-   v3_add( (v3f){0.0f, scale,0.0f}, pos, p0 );
-   v3_add( (v3f){0.0f,-scale,0.0f}, pos, p1 );
-   vg_line( p0, p1, colour );
-   v3_add( (v3f){0.0f,0.0f, scale}, pos, p0 );
-   v3_add( (v3f){0.0f,0.0f,-scale}, pos, p1 );
-   vg_line( p0, p1, colour );
-}
-
-static void vg_line_point( v3f pt, float size, u32 colour ){
-   if( !vg_lines.enabled ) return;
-
-   boxf box =
-   {
-      { pt[0]-size, pt[1]-size, pt[2]-size },
-      { pt[0]+size, pt[1]+size, pt[2]+size }
-   };
-
-   vg_line_boxf( box, colour );
-}
-
-
-static void vg_line_sphere( m4x3f m, float radius, u32 colour ){
-   if( !vg_lines.enabled ) return;
-
-   v3f ly = { 0.0f, 0.0f, radius },
-       lx = { 0.0f, radius, 0.0f },
-       lz = { 0.0f, 0.0f, radius };
-   
-   for( int i=0; i<16; i++ ){
-      float t = ((float)(i+1) * (1.0f/16.0f)) * VG_PIf * 2.0f,
-            s = sinf(t),
-            c = cosf(t);
-
-      v3f py = { s*radius, 0.0f, c*radius },
-          px = { s*radius, c*radius, 0.0f },
-          pz = { 0.0f, s*radius, c*radius };
-
-      v3f p0, p1, p2, p3, p4, p5;
-      m4x3_mulv( m, py, p0 );
-      m4x3_mulv( m, ly, p1 );
-      m4x3_mulv( m, px, p2 );
-      m4x3_mulv( m, lx, p3 );
-      m4x3_mulv( m, pz, p4 );
-      m4x3_mulv( m, lz, p5 );
-
-      vg_line( p0, p1, colour == 0x00? 0xff00ff00: colour );
-      vg_line( p2, p3, colour == 0x00? 0xff0000ff: colour );
-      vg_line( p4, p5, colour == 0x00? 0xffff0000: colour );
-
-      v3_copy( py, ly );
-      v3_copy( px, lx );
-      v3_copy( pz, lz );
-   }
-}
-
-static void vg_line_capsule( m4x3f m, float radius, float h, u32 colour ){
-   if( !vg_lines.enabled ) return;
-   
-   v3f ly = { 0.0f, 0.0f, radius },
-       lx = { 0.0f, radius, 0.0f },
-       lz = { 0.0f, 0.0f, radius };
-
-   float s0 = sinf(0.0f)*radius,
-         c0 = cosf(0.0f)*radius;
-
-   v3f p0, p1, up, right, forward;
-   m3x3_mulv( m, (v3f){0.0f,1.0f,0.0f}, up );
-   m3x3_mulv( m, (v3f){1.0f,0.0f,0.0f}, right );
-   m3x3_mulv( m, (v3f){0.0f,0.0f,-1.0f}, forward );
-   v3_muladds( m[3], up, -h*0.5f+radius, p0 );
-   v3_muladds( m[3], up,  h*0.5f-radius, p1 );
-
-   v3f a0, a1, b0, b1;
-   v3_muladds( p0, right, radius, a0 );
-   v3_muladds( p1, right, radius, a1 );
-   v3_muladds( p0, forward, radius, b0 );
-   v3_muladds( p1, forward, radius, b1 );
-   vg_line( a0, a1, colour );
-   vg_line( b0, b1, colour );
-
-   v3_muladds( p0, right, -radius, a0 );
-   v3_muladds( p1, right, -radius, a1 );
-   v3_muladds( p0, forward, -radius, b0 );
-   v3_muladds( p1, forward, -radius, b1 );
-   vg_line( a0, a1, colour );
-   vg_line( b0, b1, colour );
-   
-   for( int i=0; i<16; i++ ){
-      float t = ((float)(i+1) * (1.0f/16.0f)) * VG_PIf * 2.0f,
-            s1 = sinf(t)*radius,
-            c1 = cosf(t)*radius;
-
-      v3f e0 = { s0, 0.0f, c0 },
-          e1 = { s1, 0.0f, c1 },
-          e2 = { s0, c0, 0.0f },
-          e3 = { s1, c1, 0.0f },
-          e4 = { 0.0f, c0, s0 },
-          e5 = { 0.0f, c1, s1 };
-
-      m3x3_mulv( m, e0, e0 );
-      m3x3_mulv( m, e1, e1 );
-      m3x3_mulv( m, e2, e2 );
-      m3x3_mulv( m, e3, e3 );
-      m3x3_mulv( m, e4, e4 );
-      m3x3_mulv( m, e5, e5 );
-
-      v3_add( p0, e0, a0 );
-      v3_add( p0, e1, a1 );
-      v3_add( p1, e0, b0 );
-      v3_add( p1, e1, b1 );
-
-      vg_line( a0, a1, colour );
-      vg_line( b0, b1, colour );
-
-      if( c0 < 0.0f ){
-         v3_add( p0, e2, a0 );
-         v3_add( p0, e3, a1 );
-         v3_add( p0, e4, b0 );
-         v3_add( p0, e5, b1 );
-      }
-      else{
-         v3_add( p1, e2, a0 );
-         v3_add( p1, e3, a1 );
-         v3_add( p1, e4, b0 );
-         v3_add( p1, e5, b1 );
-      }
-
-      vg_line( a0, a1, colour );
-      vg_line( b0, b1, colour );
-
-      s0 = s1;
-      c0 = c1;
-   }
-}
-
-#endif /* VG_LINES_H */
+extern vg_lines;
+
+void vg_line_capsule( m4x3f m, float radius, float h, u32 colour );
+void vg_line_sphere( m4x3f m, float radius, u32 colour );
+void vg_line_point( v3f pt, float size, u32 colour );
+void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour );
+void vg_line_boxf( boxf box, u32 colour );
+void vg_line_mesh( v3f verts[], u32 indices[][2], u32 indice_count,u32 colour );
+void vg_line_box_verts( boxf box, v3f verts[8] );
+void vg_line_cross(v3f pos,u32 colour, float scale);
+void vg_line_arrow( line_co co, line_co dir, float size, u32 colour );
+void vg_line( line_co from, line_co to, u32 colour );
+void vg_line2( line_co from, line_co to, u32 fc, u32 tc );
+void vg_lines_drawall( void );
+void vg_lines_init(void);
diff --git a/vg_loader.c b/vg_loader.c
new file mode 100644 (file)
index 0000000..86c7417
--- /dev/null
@@ -0,0 +1,188 @@
+#include "vg_loader.h"
+#include "vg_shader.h"
+
+struct vg_loader vg_loader;
+
+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;"
+
+         "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);"
+      "}"
+   }
+};
+
+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<vg_loader.step_count; i++ )
+   {
+      struct loader_free_step *step = 
+         &vg_loader.step_buffer[vg_loader.step_count -1 -i];
+
+      vg_info( " -> %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? */
+}
index 5d6d81a210ecbbd92553e6f992607a0714e6b07d..ec8fb0d81a765d86626e0e442e7d9f2daf6a2769 100644 (file)
@@ -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<vg_loader.step_count; i++ )
-   {
-      struct loader_free_step *step = 
-         &vg_loader.step_buffer[vg_loader.step_count -1 -i];
-
-      vg_info( " -> %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<lines_to_draw; i ++ ){
-      ptr --;
-
-      if( ptr < 0 )
-         ptr = vg_list_size( vg_log.buffer )-1;
-      
-      ui_text( vg_uictx.cursor, vg_log.buffer[ptr], 1, 0 );
-      vg_uictx.cursor[1] -= fh;
-   }
-
-   SDL_AtomicUnlock( &log_print_sl );
-
-   ui_resolve();
-   ui_draw( NULL );
-#endif
-}
+extern vg_loader;
 
-static 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 );
-}
-
-static 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_render_log();
-   vg.loader_ring = 1.0f;
-}
-
-
-static void vg_load_full(void);
-
-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;
-}
-
-static int vg_loader_availible(void){
-   if( SDL_SemValue( vg.sem_loader ) ){
-      if( !(vg_async.start) )
-         return 1;
-   }
-
-   return 0;
-}
-
-static 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
- */
-static 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? */
-}
+void vg_loader_start( void(*pfn)(void *data), void *data );
+void _vg_loader_step( void( *fn_load )(void), void( *fn_free )(void),
+                      const char *alias );
+int vg_loader_availible(void);
+void vg_loader_render(void);
+void vg_loader_render_ring( f32 opacity );
+static void vg_loader_free(void);
+void vg_loader_init(void);
 
 #define vg_loader_step( FN, FN_FREE )\
    _vg_loader_step( FN, FN_FREE, #FN )
-
-#endif /* VG_LOADER_H */
index 638d63feaa13172d3df2e7cde302c38f34a77d2d..84807daeaca72e1ee0f96b7d7ae3361ad86173e9 100644 (file)
--- a/vg_log.c
+++ b/vg_log.c
@@ -1,16 +1,18 @@
-#ifndef VG_LOG_C
 #include <stdarg.h>
 #include <string.h>
 #include <malloc.h>
-#include "vg_stdint.h"
 #include "vg_platform.h"
 #include "vg_log.h"
+#include "vg_string.h"
 
 #ifndef _WIN32
  #include <execinfo.h>
 #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<size; i++ )
@@ -132,5 +136,3 @@ static void vg_print_backtrace(void){
 
 #endif
 }
-
-#endif /* VG_LOG_C */
index f80c3b366c528f4d35fd1614d9ae03eee0145aae..cf2b7370301b8de0db02a163b0e2ebda9c648c43 100644 (file)
--- a/vg_log.h
+++ b/vg_log.h
@@ -1,8 +1,7 @@
-#ifndef VG_LOG_H
-#define VG_LOG_H
+#pragma once
 
+#include "vg_platform.h"
 #include <stdio.h>
-#include "vg_stdint.h"
 
 #define VG_LOG_MCSTR(S) VG_LOG_MCSTR2(S)
 #define VG_LOG_MCSTR2(S) #S
  #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 3d4f53ffd53eca654a003663be5a014ea43238d3..4af60c8c84adc1022d424071ef4438130ab8ee88 100644 (file)
--- 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 <math.h>
@@ -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 (file)
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 <math.h>
+#include <stdlib.h>
+
+#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->index<MT_STATE_VECTOR_LENGTH; rand->index++){
+      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; kk<MT_STATE_VECTOR_LENGTH-MT_STATE_VECTOR_M; kk++ ){
+         y = (rand->mt[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( ; kk<MT_STATE_VECTOR_LENGTH-1; kk++ ){
+         y = (rand->mt[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 (file)
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 <stdlib.h>
+#include <malloc.h>
+
+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; i<size; i++ ){
+      bytes[i] = 0xfe;
+   }
+
+   alloc->allocation_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; i<alloc->allocation_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; i<depth; i++ ) printf( "  " );
+      printf( "LA(%s): %u bytes, %f%% used\n", name, s, p );
+
+      if( alloc->flags & VG_MEMORY_SYSTEM ){
+         for( u32 i=0; i<alloc->allocation_count; i++ ){
+            vg_allocation_meta *meta = &alloc->alloc_table[i];
+            
+            if( meta->type == k_allocation_type_block ){
+               for( int i=0; i<depth+1; i++ ) printf( "  " );
+               printf( "B(%s): %u bytes\n", meta->name, meta->size );
+            }
+            else{
+               vg_mem_log( meta->data, depth +1, meta->name );
+            }
+         }
+      }
+      else{
+         for( int i=0; i<depth+1; i++ ) printf( "  " );
+         printf( "<opaque memory> (UNTRACKED)\n" );
+      }
+   }
+   else{
+      vg_error( "allocations are not tracked (turn on libc mode)\n" );
+   }
+}
index 6aa22f273abfec1878cd22914a295540149d8d83..6fde883b7643e7c91ad5b58a35ee63e35679082f 100644 (file)
--- 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 <stdlib.h>
-#include <malloc.h>
-
-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; i<size; i++ ){
-      bytes[i] = 0xfe;
-   }
-
-   alloc->allocation_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; i<alloc->allocation_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; i<depth; i++ ) printf( "  " );
-      printf( "LA(%s): %u bytes, %f%% used\n", name, s, p );
-
-      if( alloc->flags & VG_MEMORY_SYSTEM ){
-         for( u32 i=0; i<alloc->allocation_count; i++ ){
-            vg_allocation_meta *meta = &alloc->alloc_table[i];
-            
-            if( meta->type == k_allocation_type_block ){
-               for( int i=0; i<depth+1; i++ ) printf( "  " );
-               printf( "B(%s): %u bytes\n", meta->name, meta->size );
-            }
-            else{
-               vg_mem_log( meta->data, depth +1, meta->name );
-            }
-         }
-      }
-      else{
-         for( int i=0; i<depth+1; i++ ) printf( "  " );
-         printf( "<opaque memory> (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 (file)
index 0000000..1fe3302
--- /dev/null
@@ -0,0 +1,100 @@
+#include "vg_platform.h"
+#include "vg_mem.h"
+#include "vg_mem_pool.h"
+#include <stddef.h>
+
+/* 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;
+   }
+}
index 412d2c0a3b11be67d43c5e75f97a80d80bc3fb52..3a61881ddca69ae9584f3243bc6121ebb318c993 100644 (file)
-#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 (file)
index 0000000..3b1a9d6
--- /dev/null
@@ -0,0 +1,77 @@
+#include "vg_platform.h"
+#include "vg_mem.h"
+#include "vg_mem_queue.h"
+#include <stddef.h>
+#include <string.h>
+
+/*
+ * 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);
+}
index 1b556ae9affdfe5c5a1d7919a7579bfbc07894c7..f898ada2b7fb0da1139ac4cbe69d65b79c80cee7 100644 (file)
@@ -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 (file)
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; i<len; i++ ){
+      msg->buf[ 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; i<len; i++ ){
+      buf[i] = msg->buf[ 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; i<VG_MIN(src_n,dst_n); i ++ ){
+         const void *ptr_s = src + i*src_s;
+         void *ptr_d = dst + i*dst_s;
+
+         if( dst_b == k_vg_msg_unsigned ){
+            u64 a = vg_msg_cast_to_u64( ptr_s, src_b, src_s );
+            if     ( dst_s == 1 ) *((u8 *)ptr_d) = (u8 )a;
+            else if( dst_s == 2 ) *((u16*)ptr_d) = (u16)a;
+            else if( dst_s == 4 ) *((u32*)ptr_d) = (u32)a;
+            else if( dst_s == 8 ) *((u64*)ptr_d) = a;
+         }
+         else if( dst_b == k_vg_msg_signed ){
+            i64 a = vg_msg_cast_to_i64( ptr_s, src_b, src_s );
+            if     ( dst_s == 1 ) *((i8 *)ptr_d) = (i8 )a;
+            else if( dst_s == 2 ) *((i16*)ptr_d) = (i16)a;
+            else if( dst_s == 4 ) *((i32*)ptr_d) = (i32)a;
+            else if( dst_s == 8 ) *((i64*)ptr_d) = a;
+         }
+         else {
+            f64 a = vg_msg_cast_to_f64( ptr_s, src_b, src_s );
+            if     ( dst_s == 4 ) *((f32*)ptr_d) = (f32)a;
+            else if( dst_s == 8 ) *((f64*)ptr_d) = a;
+         }
+      }
+   }
+}
+
+/*
+ * search in current level from cursor, to find kv cmd
+ * returns 0 if not found
+ * Cursor does not move
+ * If found, the kv command is written to cmd
+ */
+int vg_msg_getkvcmd( vg_msg *msg, const char *key, vg_msg_cmd *cmd )
+{
+   vg_msg_cursor orig = msg->cur;
+   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<b.cur.depth-1; i++ ) printf( "  " );
+         printf( "'%s'{\n", cmd.key );
+      }
+      else {
+         for( u32 i=0; i<b.cur.depth; i++ ) printf( "  " );
+
+         if( cmd.code == k_vg_msg_endframe )
+            printf( "}\n" );
+         else if( cmd.code == k_vg_msg_kvstring )
+            printf( "'%s': '%s'\n", cmd.key, (char *)cmd.value );
+         else if( cmd.code == k_vg_msg_kvbin )
+            printf( "'%s': <binary data> (%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<count; i++ ){
+               const void *p = cmd.value + size*i;
+
+               if( base == k_vg_msg_unsigned ){
+                  printf( 
+#ifdef _WIN32
+                  "%llu"
+#else
+                  "%lu"
+#endif
+                  , vg_msg_cast_to_u64( p, base, size ) );
+               }
+               else if( base == k_vg_msg_signed ){
+                  printf(
+#ifdef _WIN32
+                  "%lld"
+#else
+                  "%ld"
+#endif
+                  , vg_msg_cast_to_i64( p, base, size ) );
+               }
+               else 
+                  printf( "%f", vg_msg_cast_to_f64( p, base, size ));
+
+               if( i+1<count ) printf(", ");
+            }
+
+            if( count > 1 ) printf( " }" );
+            printf( "\n" );
+         }
+      }
+   }
+}
index b5fb4a482bf991ef4624b65ab93b89be33da51a6..91f0dc20a8cc22e59af0ff4517f4ca626e62e3b2 100644 (file)
--- 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; i<len; i++ ){
-      msg->buf[ 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; i<len; i++ ){
-      buf[i] = msg->buf[ 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; i<VG_MIN(src_n,dst_n); i ++ ){
-         const void *ptr_s = src + i*src_s;
-         void *ptr_d = dst + i*dst_s;
-
-         if( dst_b == k_vg_msg_unsigned ){
-            u64 a = vg_msg_cast_to_u64( ptr_s, src_b, src_s );
-            if     ( dst_s == 1 ) *((u8 *)ptr_d) = (u8 )a;
-            else if( dst_s == 2 ) *((u16*)ptr_d) = (u16)a;
-            else if( dst_s == 4 ) *((u32*)ptr_d) = (u32)a;
-            else if( dst_s == 8 ) *((u64*)ptr_d) = a;
-         }
-         else if( dst_b == k_vg_msg_signed ){
-            i64 a = vg_msg_cast_to_i64( ptr_s, src_b, src_s );
-            if     ( dst_s == 1 ) *((i8 *)ptr_d) = (i8 )a;
-            else if( dst_s == 2 ) *((i16*)ptr_d) = (i16)a;
-            else if( dst_s == 4 ) *((i32*)ptr_d) = (i32)a;
-            else if( dst_s == 8 ) *((i64*)ptr_d) = a;
-         }
-         else {
-            f64 a = vg_msg_cast_to_f64( ptr_s, src_b, src_s );
-            if     ( dst_s == 4 ) *((f32*)ptr_d) = (f32)a;
-            else if( dst_s == 8 ) *((f64*)ptr_d) = a;
-         }
-      }
-   }
-}
-
-/*
- * search in current level from cursor, to find kv cmd
- * returns 0 if not found
- * Cursor does not move
- * If found, the kv command is written to cmd
- */
-static int vg_msg_getkvcmd( vg_msg *msg, const char *key, vg_msg_cmd *cmd ){
-   vg_msg_cursor orig = msg->cur;
-   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<b.cur.depth-1; i++ ) printf( "  " );
-         printf( "'%s'{\n", cmd.key );
-      }
-      else {
-         for( u32 i=0; i<b.cur.depth; i++ ) printf( "  " );
-
-         if( cmd.code == k_vg_msg_endframe )
-            printf( "}\n" );
-         else if( cmd.code == k_vg_msg_kvstring )
-            printf( "'%s': '%s'\n", cmd.key, (char *)cmd.value );
-         else if( cmd.code == k_vg_msg_kvbin )
-            printf( "'%s': <binary data> (%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<count; i++ ){
-               const void *p = cmd.value + size*i;
-
-               if( base == k_vg_msg_unsigned ){
-                  printf( 
-#ifdef _WIN32
-                  "%llu"
-#else
-                  "%lu"
-#endif
-                  , vg_msg_cast_to_u64( p, base, size ) );
-               }
-               else if( base == k_vg_msg_signed ){
-                  printf(
-#ifdef _WIN32
-                  "%lld"
-#else
-                  "%ld"
-#endif
-                  , vg_msg_cast_to_i64( p, base, size ) );
-               }
-               else 
-                  printf( "%f", vg_msg_cast_to_f64( p, base, size ));
-
-               if( i+1<count ) printf(", ");
-            }
-
-            if( count > 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 (file)
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 <stdlib.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;
+
+/* 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;
+}
index c817d0674c011e4ac6ea761e5154176637c66f9b..6f2d8792f8acd230230fc3d8befc8ce4921dcec3 100644 (file)
--- a/vg_opt.h
+++ b/vg_opt.h
 /*
- * 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 <stdlib.h>
-#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 (file)
index 0000000..6b8b087
--- /dev/null
@@ -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<octaves; i++ ){
+               div += 256 * amp;
+               fin += vg_perlin_noise_1d( xa, seed ) * amp;
+               amp /= 2.f;
+               xa *= 2.f;
+       }
+       
+       return fin/div;
+}
+
+f32 vg_perlin_fract_2d( f32 x, f32 y, f32 freq, int octaves, int seed )
+{
+       f32 xa  = x*freq,
+           ya  = y*freq,
+           amp = 1.0f,
+           fin = 0.f,
+           div = 0.f;
+       
+       for( int i=0; i<octaves; i++ ){
+               div += 256 * amp;
+               fin += vg_perlin_noise_2d( xa, ya, seed ) * amp;
+               amp /= 2;
+               xa *= 2;
+               ya *= 2;
+       }
+       
+       return fin/div;
+}
index 6047620a170bf1ac419cc2eb6bad70292614b566..1d28c769360606065d163cb15836c8ea0b29de34 100644 (file)
@@ -1,155 +1,6 @@
-#ifndef PERLIN_H
-#define PERLIN_H
+#pragma once
 
-#include "vg/vg_m.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 float perlin_smooth( float x, float y, float t )
-{
-       return vg_lerpf( x, y, t*t*(3.0f-2.0f*t) );
-}
-
-static float perlin_noise2d( float x, float y, int seed )
-{
-       int ix = x, iy = y;
-       float 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 );
-
-       float low  = perlin_smooth( s,t,x_frac ),
-             high = perlin_smooth( u,v,x_frac );
-
-       return perlin_smooth( low, high, y_frac );
-}
-
-static float perlin_noise( float v, int seed )
-{
-       int iv = v;
-       float frac = v-iv;
-       int s = perlin_noise1( iv,   seed ),
-           t = perlin_noise1( iv+1, seed );
-
-       return perlin_smooth( s, t, frac );
-}
-
-static float perlin1d( float v, float freq, int octaves, int seed )
-{
-       float xa  = v*freq,
-             amp = 1.0f,
-             fin = 0.f,
-             div = 0.f;
-       
-       for( int i=0; i<octaves; i++ ){
-               div += 256 * amp;
-               fin += perlin_noise( xa, seed ) * amp;
-               amp /= 2.f;
-               xa *= 2.f;
-       }
-       
-       return fin/div;
-}
-
-static float perlin2d( float x, float y, float freq, int octaves, int seed )
-{
-       float xa  = x*freq,
-             ya  = y*freq,
-             amp = 1.0f,
-             fin = 0.f,
-             div = 0.f;
-       
-       for( int i=0; i<octaves; i++ ){
-               div += 256 * amp;
-               fin += perlin_noise2d( xa, ya, seed ) * amp;
-               amp /= 2;
-               xa *= 2;
-               ya *= 2;
-       }
-       
-       return fin/div;
-}
-#endif
+f32 vg_perlin_noise2d( f32 x, f32 y, int seed );
+f32 vg_perlin_noise1d( f32 v, int seed );
+f32 vg_perlin_fract_1d( f32 v, f32 freq, int octaves, int seed );
+f32 vg_perlin_fract_2d( f32 x, f32 y, f32 freq, int octaves, int seed );
index e8b288e9731afd4034d922ff010a5ed3e4054700..5ec7287ab436d692f8fb40141627b790ed423cd5 100644 (file)
@@ -1,25 +1,7 @@
-#ifndef VG_PLATFORM_H
-#define VG_PLATFORM_H
+#pragma once
 
-//#include "vg.h"
+#if 0
 #include "vg_stdint.h"
-
-/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
-
-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];
-
 // Resource types
 typedef struct vg_tex2d vg_tex2d;
 
@@ -30,9 +12,9 @@ struct vg_achievement
 };
 
 #define vg_static_assert _Static_assert
-#define vg_list_size( A ) (sizeof(A)/sizeof(A[0]))
 #define VG_MUST_USE_RESULT __attribute__((warn_unused_result))
 
+
 #include <stdio.h>
 #include <string.h>
 #include <stdarg.h>
@@ -45,60 +27,39 @@ struct vg_achievement
 #include <stdio.h>
 #include <errno.h>
 #include <stdlib.h>
+#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<len; i++ ){
-      dst[i] = src[i];
-
-      if( !src[i] ) return i;
-
-      if( i == len-1 ){
-         if( behaviour == k_strncpy_always_add_null ){
-            dst[i] = '\0';
-            return i;
-         }
-         else if( behaviour == k_strncpy_overflow_fatal ){
-            vg_fatal_error( "Strncpy dest exceeded buffer length\n" );
-         }
-      }
-   }
-
-   return 0;
-}
-
-static u32 vg_strdjb2( const char *str ){
-   u32 hash = 5381, c;
-
-   while( (c = *str++) )
-      hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
-
-   return hash;
-}
-
-static int vg_strdjb2_eq( const char *s1, u32 h1, 
-                             const char *s2, u32 h2 )
-{
-   if( h1 == h2 ){
-      if(!strcmp(s1, s2)) return 1;
-      else return 0;
-   } else return 0;
-}
+#include <stdlib.h>
+#include <stdint.h>
+
+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 (file)
index 0000000..d203c92
--- /dev/null
@@ -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; i<count; i ++ ){
+      avgs[i] = 0.0;
+      colours[i] = ui_colour( k_ui_red + ((i*3)&0xe) );
+   }
+
+   f64 rate_mul = 1000.0 / (f64)SDL_GetPerformanceFrequency();
+
+   for( i32 i=0; i<VG_PROFILE_SAMPLE_COUNT; i++ ){
+      f64 total = 0.0;
+
+      if( normalize ){
+         budget = 0.0;
+         for( u32 j=0; j<count; j++ )
+            budget += (f64)profiles[j]->samples[i] * rate_mul;
+      }
+
+      for( int j=0; j<count; j++ ){
+         f64 sample  = (f64)profiles[j]->samples[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; i<count; i++ ){
+      snprintf( infbuf, 64, "%.4fms %s", 
+                        avgs[i] * (1.0f/(VG_PROFILE_SAMPLE_COUNT-1)),
+                        profiles[i]->name );
+
+      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 );
+}
index 699e2a72eadfdacac40abac419c53737554c34dd..74190cb34b0ac27ce97fd35ef4a5af6e301e61f2 100644 (file)
@@ -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; i<count; i ++ ){
-      avgs[i] = 0.0;
-      colours[i] = ui_colour( k_ui_red + ((i*3)&0xe) );
-   }
-
-   f64 rate_mul = 1000.0 / (f64)SDL_GetPerformanceFrequency();
-
-   for( i32 i=0; i<VG_PROFILE_SAMPLE_COUNT; i++ ){
-      f64 total = 0.0;
-
-      if( normalize ){
-         budget = 0.0;
-         for( u32 j=0; j<count; j++ )
-            budget += (f64)profiles[j]->samples[i] * rate_mul;
-      }
-
-      for( int j=0; j<count; j++ ){
-         f64 sample  = (f64)profiles[j]->samples[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; i<count; i++ ){
-      snprintf( infbuf, 64, "%.4fms %s", 
-                        avgs[i] * (1.0f/(VG_PROFILE_SAMPLE_COUNT-1)),
-                        profiles[i]->name );
-
-      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 (file)
index 0000000..bcfebc5
--- /dev/null
@@ -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 <math.h>
+
+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 );
+}
index f6b9417a2b90e2fabb62b37d0d2fcc87cf5888fd..70be483dbe8371c64b691970e6c33ede9d46b495 100644 (file)
@@ -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 <math.h>
-
-/*
- * -----------------------------------------------------------------------------
- *                                (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 (file)
index 0000000..f70a39b
--- /dev/null
@@ -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<count; i++ )
+      v3_copy( n, buf[i].n );
+
+   return count;
+}
+
+int rb_global_has_space( void )
+{
+   if( rb_contact_count + 16 > 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; i<len; i++ ){
+      rb_ct *ct = &man[i];
+
+      if( ct->type == 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; i<len-1; i++ ){
+      rb_ct *ci = &man[i];
+      if( ci->type != k_contact_type_edge ) 
+         continue;
+
+      for( int j=i+1; j<len; j++ ){
+         rb_ct *cj = &man[j];
+         if( cj->type != 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; i<len-1; i++ ){
+      rb_ct *ci = &man[i];
+      int similar = 0;
+
+      if( ci->type == k_contact_type_disabled ) continue;
+
+      for( int j=i+1; j<len; j++ ){
+         rb_ct *cj = &man[j];
+
+         if( cj->type == 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; i<len; i++ ){
+      rb_ct *ct = &man[i];
+      if( ct->type == 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; i<len; i++ ){
+      rb_ct *ci = &man[i];
+      if( ci->type == k_contact_type_disabled ||
+          ci->type == k_contact_type_edge ) 
+         continue;
+
+      float d1 = v3_dot( ci->co, ci->n );
+
+      for( int j=0; j<len; j++ ){
+         if( j == i )
+            continue;
+
+         rb_ct *cj = &man[j];
+         if( cj->type == 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; i<len; i++ ){
+         rb_ct *ct = &manifold[i];
+
+         float resolved_amt = v3_dot( ct->n, 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; i<len; i++ ){
+      rb_ct *ct = &buffer[i];
+      rb_prepare_contact( ct, dt );
+
+      v3f ra, rb, raCn, rbCn, raCt, rbCt;
+      v3_sub( ct->co, 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; i<len; i++ ){
+      rb_ct *ct = &buf[i];
+
+      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 );
+      
+      /* 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 );
+   }
+}
index c08baa536a5e55714d98fd7a39393d8e2098ed7f..e0cf3e378e6a7b0f78c597a3dbc50132c5f81e0c 100644 (file)
@@ -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<count; i++ )
-      v3_copy( n, buf[i].n );
-
-   return count;
-}
-
-static int rb_global_has_space( void ){
-   if( rb_contact_count + 16 > 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; i<len; i++ ){
-      rb_ct *ct = &man[i];
-
-      if( ct->type == 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; i<len-1; i++ ){
-      rb_ct *ci = &man[i];
-      if( ci->type != k_contact_type_edge ) 
-         continue;
-
-      for( int j=i+1; j<len; j++ ){
-         rb_ct *cj = &man[j];
-         if( cj->type != 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; i<len-1; i++ ){
-      rb_ct *ci = &man[i];
-      int similar = 0;
-
-      if( ci->type == k_contact_type_disabled ) continue;
-
-      for( int j=i+1; j<len; j++ ){
-         rb_ct *cj = &man[j];
-
-         if( cj->type == 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; i<len; i++ ){
-      rb_ct *ct = &man[i];
-      if( ct->type == 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; i<len; i++ ){
-      rb_ct *ci = &man[i];
-      if( ci->type == k_contact_type_disabled ||
-          ci->type == k_contact_type_edge ) 
-         continue;
-
-      float d1 = v3_dot( ci->co, ci->n );
-
-      for( int j=0; j<len; j++ ){
-         if( j == i )
-            continue;
-
-         rb_ct *cj = &man[j];
-         if( cj->type == 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; i<len; i++ ){
-         rb_ct *ct = &manifold[i];
-
-         float resolved_amt = v3_dot( ct->n, 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; i<len; i++ ){
-      rb_ct *ct = &buffer[i];
-      rb_prepare_contact( ct );
-
-      v3f ra, rb, raCn, rbCn, raCt, rbCt;
-      v3_sub( ct->co, 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; i<len; i++ ){
-      rb_ct *ct = &buf[i];
-
-      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 );
-      
-      /* 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 (file)
index 0000000..3d6b932
--- /dev/null
@@ -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; i<len; i++ ){
+      rb_constr_pos *constr = &buffer[i];
+      rigidbody *rba = constr->rba, *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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[ i ];
+      
+      v3f vx, vy, va, vxb, axis, center;
+
+      m3x3_mulv( st->rba->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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[ i ];
+      
+      v3f vx, vxb, vy, va, axis, center;
+
+      m3x3_mulv( st->rba->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; i<len; i++ ){
+      rb_constr_pos *constr = &buf[i];
+      rigidbody *rba = constr->rba, *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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[ i ];
+
+      if( !st->axis_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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[ i ];
+
+      if( !st->tangent_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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[ i ];
+
+      if( !st->axis_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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[ i ];
+
+      if( !st->tangent_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; i<len; i++ ){
+      rb_constr_pos *constr = &buf[i];
+      rigidbody *rba = constr->rba, *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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[i];
+
+      if( !st->tangent_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; i<len; i++ ){
+      rb_constr_swingtwist *st = &buf[i];
+
+      if( !st->axis_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
+      }
+   }
+}
index 410b14fe84a6c0d083c848c86f6073b5408ffa1d..92ec26d9e30e1388b9e664ae6b69c6e2e275dfb2 100644 (file)
@@ -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; i<len; i++ ){
-      rb_constr_pos *constr = &buffer[i];
-      rigidbody *rba = constr->rba, *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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[ i ];
-      
-      v3f vx, vy, va, vxb, axis, center;
-
-      m3x3_mulv( st->rba->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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[ i ];
-      
-      v3f vx, vxb, vy, va, axis, center;
-
-      m3x3_mulv( st->rba->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; i<len; i++ ){
-      rb_constr_pos *constr = &buf[i];
-      rigidbody *rba = constr->rba, *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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[ i ];
-
-      if( !st->axis_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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[ i ];
-
-      if( !st->tangent_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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[ i ];
-
-      if( !st->axis_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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[ i ];
-
-      if( !st->tangent_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; i<len; i++ ){
-      rb_constr_pos *constr = &buf[i];
-      rigidbody *rba = constr->rba, *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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[i];
-
-      if( !st->tangent_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; i<len; i++ ){
-      rb_constr_swingtwist *st = &buf[i];
-
-      if( !st->axis_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 (file)
index 0000000..b14df65
--- /dev/null
@@ -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<H; x ++ ){
+      tris[tri_index*3+0] = base+0;
+      tris[tri_index*3+1] = base+1+x;
+      tris[tri_index*3+2] = base+1+((x+1)%H);
+      tri_index += 1;
+   }
+
+   for( u32 y=1; y<V-1; y ++ ){
+      f32 ty = ((f32)y/(f32)(V-1)) * VG_PIf;
+      u32 ybase = y-1;
+      for( u32 x=0; x<H; x ++ ){
+         f32 tx = ((f32)x/(f32)H) * VG_TAUf;
+
+         v4f co = { cosf(tx)*sinf(ty), -cosf(ty), sinf(tx)*sinf(ty), y>=(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<H; x ++ ){
+      tris[tri_index*3+0] = base + (H*(V-2) + 2)-1;
+      tris[tri_index*3+1] = base+1 + (V-3)*H+((x+1)%H);
+      tris[tri_index*3+2] = base+1 + (V-3)*H+x;
+      tri_index += 1;
+   }
+
+   vg_async_dispatch( call, async_vg_rb_view_init );
+}
+
+void vg_rb_view_bind(void)
+{
+   glEnable( GL_CULL_FACE );
+   glEnable( GL_DEPTH_TEST );
+
+       glUseProgram( _shader_rigidbody.id );
+   glUniformMatrix4fv( glGetUniformLocation( _shader_rigidbody.id, "uPv" ), 
+     1, GL_FALSE, (float *)vg.pv );
+
+       glBindVertexArray( vg_rb_view.vao );
+}
+
+void vg_rb_view_box( m4x3f mdl, boxf bbx, v4f colour )
+{
+   v3f e;
+   v3_sub( bbx[1], bbx[0], e );
+   v3_muls( e, 0.5f, e );
+
+   m4x3f mmdl;
+   m4x3_identity( mmdl );
+   m3x3_scale( mmdl, e );
+   v3_add( bbx[0], e, mmdl[3] );
+   m4x3_mul( mdl, mmdl, mmdl );
+
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
+                 colour );
+       glDrawElements( GL_TRIANGLES, 
+                   vg_rb_view.box_count*3, GL_UNSIGNED_SHORT, 
+                   (void *)(vg_rb_view.box_start*3*sizeof(u16)) );
+}
+
+void vg_rb_view_sphere( m4x3f mdl, f32 r, v4f colour )
+{
+   m4x3f mmdl;
+   m4x3_copy( mdl, mmdl );
+   m3x3_scalef( mmdl, r );
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
+                 colour );
+       glDrawElements( GL_TRIANGLES, 
+                   vg_rb_view.sphere_count*3, GL_UNSIGNED_SHORT, 
+                   (void *)(vg_rb_view.sphere_start*3*sizeof(u16)) );
+}
+
+void vg_rb_view_capsule( m4x3f mdl, f32 r, f32 h, v4f colour )
+{
+   m4x3f mmdl0, mmdl1;
+   m4x3_identity( mmdl0 );
+   m4x3_identity( mmdl1 );
+   m3x3_scalef( mmdl0, r );
+   m3x3_scalef( mmdl1, r );
+   mmdl0[3][1] = -h*0.5f+r;
+   mmdl1[3][1] =  h*0.5f-r;
+   m4x3_mul( mdl, mmdl0, mmdl0 );
+   m4x3_mul( mdl, mmdl1, mmdl1 );
+
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
+                         1,GL_FALSE,(float*)mmdl0);
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
+                         1,GL_FALSE,(float*)mmdl1);
+   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
+                 colour );
+       glDrawElements( GL_TRIANGLES, 
+                   vg_rb_view.sphere_count*3, GL_UNSIGNED_SHORT, 
+                   (void *)(vg_rb_view.sphere_start*3*sizeof(u16)) );
+}
index 1064fedee5f28a53c07c04f1fa85f2113e48b1ad..111ff7c2a5e87ee99a8937eb12f3e5a6df6a9463 100644 (file)
@@ -1,319 +1,8 @@
 #pragma once
 #include "vg_rigidbody.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();
-}
-
-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<H; x ++ ){
-      tris[tri_index*3+0] = base+0;
-      tris[tri_index*3+1] = base+1+x;
-      tris[tri_index*3+2] = base+1+((x+1)%H);
-      tri_index += 1;
-   }
-
-   for( u32 y=1; y<V-1; y ++ ){
-      f32 ty = ((f32)y/(f32)(V-1)) * VG_PIf;
-      u32 ybase = y-1;
-      for( u32 x=0; x<H; x ++ ){
-         f32 tx = ((f32)x/(f32)H) * VG_TAUf;
-
-         v4f co = { cosf(tx)*sinf(ty), -cosf(ty), sinf(tx)*sinf(ty), y>=(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<H; x ++ ){
-      tris[tri_index*3+0] = base + (H*(V-2) + 2)-1;
-      tris[tri_index*3+1] = base+1 + (V-3)*H+((x+1)%H);
-      tris[tri_index*3+2] = base+1 + (V-3)*H+x;
-      tri_index += 1;
-   }
-
-   vg_async_dispatch( call, async_vg_rb_view_init );
-}
-
-static void vg_rb_view_bind(void){
-   glEnable( GL_CULL_FACE );
-   glEnable( GL_DEPTH_TEST );
-
-       glUseProgram( _shader_rigidbody.id );
-   glUniformMatrix4fv( glGetUniformLocation( _shader_rigidbody.id, "uPv" ), 
-     1, GL_FALSE, (float *)vg.pv );
-
-       glBindVertexArray( vg_rb_view.vao );
-}
-
-static void vg_rb_view_box( m4x3f mdl, boxf bbx, v4f colour ){
-
-   v3f e;
-   v3_sub( bbx[1], bbx[0], e );
-   v3_muls( e, 0.5f, e );
-
-   m4x3f mmdl;
-   m4x3_identity( mmdl );
-   m3x3_scale( mmdl, e );
-   v3_add( bbx[0], e, mmdl[3] );
-   m4x3_mul( mdl, mmdl, mmdl );
-
-   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
-                         1,GL_FALSE,(float*)mmdl);
-   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
-                         1,GL_FALSE,(float*)mmdl);
-   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
-                 colour );
-       glDrawElements( GL_TRIANGLES, 
-                   vg_rb_view.box_count*3, GL_UNSIGNED_SHORT, 
-                   (void *)(vg_rb_view.box_start*3*sizeof(u16)) );
-}
-
-static void vg_rb_view_sphere( m4x3f mdl, f32 r, v4f colour ){
-   m4x3f mmdl;
-   m4x3_copy( mdl, mmdl );
-   m3x3_scalef( mmdl, r );
-   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
-                         1,GL_FALSE,(float*)mmdl);
-   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
-                         1,GL_FALSE,(float*)mmdl);
-   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
-                 colour );
-       glDrawElements( GL_TRIANGLES, 
-                   vg_rb_view.sphere_count*3, GL_UNSIGNED_SHORT, 
-                   (void *)(vg_rb_view.sphere_start*3*sizeof(u16)) );
-}
-
-static void vg_rb_view_capsule( m4x3f mdl, f32 r, f32 h, v4f colour ){
-   m4x3f mmdl0, mmdl1;
-   m4x3_identity( mmdl0 );
-   m4x3_identity( mmdl1 );
-   m3x3_scalef( mmdl0, r );
-   m3x3_scalef( mmdl1, r );
-   mmdl0[3][1] = -h*0.5f+r;
-   mmdl1[3][1] =  h*0.5f-r;
-   m4x3_mul( mdl, mmdl0, mmdl0 );
-   m4x3_mul( mdl, mmdl1, mmdl1 );
-
-   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
-                         1,GL_FALSE,(float*)mmdl0);
-   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
-                         1,GL_FALSE,(float*)mmdl1);
-   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
-                 colour );
-       glDrawElements( GL_TRIANGLES, 
-                   vg_rb_view.sphere_count*3, GL_UNSIGNED_SHORT, 
-                   (void *)(vg_rb_view.sphere_start*3*sizeof(u16)) );
-}
+void vg_rb_view_init(void);
+void vg_rb_view_bind(void);
+void vg_rb_view_box( m4x3f mdl, boxf bbx, v4f colour );
+void vg_rb_view_sphere( m4x3f mdl, f32 r, v4f colour );
+void vg_rb_view_capsule( m4x3f mdl, f32 r, f32 h, v4f colour );
diff --git a/vg_settings_menu.h b/vg_settings_menu.h
deleted file mode 100644 (file)
index 7198810..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-#ifndef VG_SETTINGS_MENU_H
-#define VG_SETTINGS_MENU_H
-
-#include "vg.h"
-#include "vg_imgui.h"
-
-#ifdef VG_GAME_SETTINGS
-static void vg_game_settings_gui( ui_rect panel ) ;
-static 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{
-      i32 new_value, *actual_value, min, max;
-      char buf[10];
-      const char *label;
-   }
-   fps_limit;
-
-   struct vg_setting_enum{
-      i32 new_value, *actual_value;
-
-      struct ui_enum_opt *options;
-      u32 option_count;
-      const char *label;
-   }
-   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<len; i ++ ){
-      if( ((buf[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;
-}
-
-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; i<vg_settings.audio_devices.option_count; i ++ ){
-            oi = &vg_settings.audio_devices.options[i];
-
-            if( oi->value == 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; i<count; i ++ ){
-      struct ui_enum_opt *oi = &options[i+1];
-
-      const char *device_name = SDL_GetAudioDeviceName( i, 0 );
-      int len = strlen(device_name);
-
-      oi->alias = 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; i<count; i ++ ){
-         struct ui_enum_opt *oi = &options[i+1];
-         if( !strcmp( oi->alias, 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 (file)
index 0000000..4b41c2e
--- /dev/null
@@ -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; i<vg_shaders.count; i ++ ){
+               vg_shader *shader = vg_shaders.shaders[i];
+
+               if( !vg_shader_compile( shader ) )
+         vg_fatal_error( "Failed to compile shader" );
+       }
+}
+
+int vg_shaders_live_recompile(int argc, const char *argv[])
+{
+   vg_info( "Recompiling shaders\n" );
+   for( int i=0; i<vg_shaders.count; i ++ )
+   {
+      struct vg_shader *shader = vg_shaders.shaders[i];
+      vg_shader_compile( shader );
+   }
+
+   return 0;
+}
+
+void vg_shader_register( struct vg_shader *shader )
+{
+   if( vg_shaders.count == vg_list_size(vg_shaders.shaders) )
+      vg_fatal_error( "Too many shaders" );
+
+   shader->compiled = 0;
+   shader->id = 0;         /* TODO: make this an error shader */
+   vg_shaders.shaders[ vg_shaders.count ++ ] = shader;
+}
index 211086ab13492849aafc7805084e72e502ae7911..059ed8ef2cc0aa228674d659c3dc9f2f3ba58385 100644 (file)
@@ -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; i<vg_shaders.count; i ++ ){
-               vg_shader *shader = vg_shaders.shaders[i];
-
-               if( !vg_shader_compile( shader ) )
-         vg_fatal_error( "Failed to compile shader" );
-       }
-}
-
-static int vg_shaders_live_recompile(int argc, const char *argv[])
-{
-   vg_info( "Recompiling shaders\n" );
-   for( int i=0; i<vg_shaders.count; i ++ )
-   {
-      struct vg_shader *shader = vg_shaders.shaders[i];
-      vg_shader_compile( shader );
-   }
-
-   return 0;
-}
-
-static void vg_shader_register( struct vg_shader *shader )
-{
-   if( vg_shaders.count == vg_list_size(vg_shaders.shaders) )
-      vg_fatal_error( "Too many shaders" );
-
-   shader->compiled = 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 (file)
index f3e9128..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef VG_STDINT_H
-#define VG_STDINT_H
-
-#include <stdint.h>
-
-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 (file)
index 0000000..a021cb1
--- /dev/null
@@ -0,0 +1,136 @@
+#include "vg_steam.h"
+#include "vg_log.h"
+#include <stdio.h>
+
+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; i<vg_steam.call_count; i++ ){
+         if( vg_steam.calls[i].id != pCallCompleted->m_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; i<vg_steam.callback_handler_count; i++ ){
+            vg_steam_callback_handler *handler = &vg_steam.callback_handlers[i];
+            if( handler->callback_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 );
+}
index 2ca5280fa8ddffe8dee90e08b6a451a8f494ebc3..db0e84dca4de676042c25f54ff776ce9a3aee5bd 100644 (file)
-#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; i<vg_steam.call_count; i++ ){
-         if( vg_steam.calls[i].id != pCallCompleted->m_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; i<vg_steam.callback_handler_count; i++ ){
-            vg_steam_callback_handler *handler = &vg_steam.callback_handlers[i];
-            if( handler->callback_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 (file)
index 0000000..2680b5b
--- /dev/null
@@ -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);
index fcdc8ae56f7d1b7908f01ac92bd4049dff416374..f0cd8cc747275aebf29ce0c7be8b4c67e6f9ffd5 100644 (file)
@@ -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 */
index 0a7f017b1f6f615048818c75f909abf35feda394..04685a86b91c3e86e418dd163f8f59ab83c0423a 100644 (file)
@@ -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 */
index 6cf467e76182876d36a99a84ba7c062130e0c5d9..384674e9bc66c5f18b4d7b07dd15725977ee2a93 100644 (file)
@@ -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 */
index 7d10278ba04005ea89c40ea4dff17020edc2f38f..da933f73e8f5bfba275f69fb7ca4d1c07b02ecf1 100644 (file)
@@ -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 */
index d724c20382cf7efd5baf6fd619069d839b0d864e..24aaf3b16b37220bfce3a81795ef4c71fcba5395 100644 (file)
@@ -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 */
index cb0bfc668c84fcccd0c221c96b8d782d5c810aa3..48594b28ca6bf4e09668daa2db1e2e993df30d64 100644 (file)
@@ -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 */
index 25ff5c06e1ed50d0c0a9ad84d7813b55bb17fdd3..6f5774e8b5e5ec41a48336fd1666b584a3103ca9 100644 (file)
@@ -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 */
index 567a7bd02331757f0d8eb9e63d63db078c9a6409..43f8bbaafd18297e47d8ec0467a1d5733afbb17c 100644 (file)
@@ -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 (file)
index ee0e91a..0000000
+++ /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; i<lvl; i++ )
-      {
-         vg_info( "  " );
-      }
-      p_show( data );
-      vg_info( " (%d) \n", t );
-
-      aatree_show_r( tree, ptnode->right, 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; i<ptnode->level; 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; i<lvl; i++ )
-         printf( "   " );
-
-   if( show )
-   {
-      p_show( data );
-
-      if( ptnode->left != 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; i<item_count; i++ )
-   {
-      aatree_pool_node *pn = aatree_get_pool_node( info, i );
-
-      if( i==item_count-1 )
-         pn->next_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 (file)
index 0000000..1a38479
--- /dev/null
@@ -0,0 +1,204 @@
+#include "vg_string.h"
+#include "vg_platform.h"
+#include <string.h>
+
+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<i; j ++ )
+         reverse[j] = temp[ i-1-j ];
+      reverse[i] = '\0';
+
+      vg_strcat( str, reverse );
+   }
+   else
+      vg_strcat( str, "0" );
+}
+
+void vg_strcati32r( vg_str *str, i32 value, i32 n, char alt )
+{
+   char temp[32];
+   i32 i=0;
+   while( value ){
+      if( i>=n ) 
+         break;
+
+      temp[ n-1 - (i ++) ] = '0' + (value % 10);
+      value /= 10;
+   }
+
+   for( ;i<n; i ++ )
+      temp[ n-1 - i ] = alt;
+
+   temp[n]='\0';
+   vg_strcat( str, temp );
+}
+
+int vg_strgood( vg_str *str )
+{
+   if( str->i == -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; i<str->i; 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; i<len; i++ ){
+      dst[i] = src[i];
+
+      if( !src[i] ) return i;
+
+      if( i == len-1 ){
+         if( behaviour == k_strncpy_always_add_null ){
+            dst[i] = '\0';
+            return i;
+         }
+         else if( behaviour == k_strncpy_overflow_fatal ){
+            vg_fatal_error( "Strncpy dest exceeded buffer length\n" );
+         }
+      }
+   }
+
+   return 0;
+}
+
+u32 vg_strdjb2( const char *str )
+{
+   u32 hash = 5381, c;
+
+   while( (c = *str++) )
+      hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+
+   return hash;
+}
+
+int vg_strdjb2_eq( const char *s1, u32 h1, const char *s2, u32 h2 )
+{
+   if( h1 == h2 ){
+      if(!strcmp(s1, s2)) return 1;
+      else return 0;
+   } else return 0;
+}
index c99fc7a65f3825ee8a8955121c1bbf494c381238..62d5abc14a38d40818b1a93bcec244d4f3111f68 100644 (file)
-#ifndef VG_STRING_H
-#define VG_STRING_H
+#pragma once
+#include "vg_platform.h"
 
 /* string builder with optional dynamic memory or static buffer. */
 
-#include "vg_stdint.h"
-
 typedef struct vg_str vg_str;
 typedef struct vg_str_dynamic vg_str_dynamic;
 
-struct vg_str{
+struct vg_str
+{
    char *buffer;
    i32 i,   /* -1: error condition. otherwise, current cursor position */
        len; /* -1: dynamically allocated. otherwise, buffer length */
 };
 
-struct vg_str_dynamic {
+struct vg_str_dynamic 
+{
    i32 len;
 };
 
 /* 
  * Returns the current storage size of the string
  */
-static 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;
-}
+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<i; j ++ )
-         reverse[j] = temp[ i-1-j ];
-      reverse[i] = '\0';
-
-      vg_strcat( str, reverse );
-   }
-   else
-      vg_strcat( str, "0" );
-}
-
-static void vg_strcati32r( vg_str *str, i32 value, i32 n, char alt ){
-   char temp[32];
-   i32 i=0;
-   while( value ){
-      if( i>=n ) 
-         break;
-
-      temp[ n-1 - (i ++) ] = '0' + (value % 10);
-      value /= 10;
-   }
-
-   for( ;i<n; i ++ )
-      temp[ n-1 - i ] = alt;
-
-   temp[n]='\0';
-   vg_strcat( str, temp );
-}
-
+void vg_strcatch( vg_str *str, char c );
+void vg_strcati32( vg_str *str, i32 value );
+void vg_strcati32r( vg_str *str, i32 value, i32 n, char alt );
 /*
  * Returns 1 if string did not overflow while building
  */
-static int vg_strgood( vg_str *str ){
-   if( str->i == -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; i<str->i; 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 (file)
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 <string.h>
+
+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 );
+}
index 3f9cd9850eb8ae45191d0dd5ddb24250e58d2bb9..24a99a0c78c34b8eab5f0e03ecefa3d1818a8036 100644 (file)
--- 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.
   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 (file)
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 (file)
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 (file)
index 0000000..b27bf4d
--- /dev/null
@@ -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 );