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" );
}
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" );
}
-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 $@
-#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];
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,
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 );
reset_racket();
}
-static void vg_pre_update(void){
+void vg_pre_update(void)
+{
vg_console.cheats = 1;
vg_lines.render = 1;
}
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 );
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){
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 );
}
}
-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 } );
-/* 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
--- /dev/null
+#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 );
+}
-/* 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;
void (*fn_runner)( void *payload, u32 size );
};
-struct vg_async{
+struct vg_async
+{
void *buffer;
vg_async_item *start, *end;
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);
--- /dev/null
+#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();
+}
-/* 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
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;
};
};
-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;
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;
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 );
--- /dev/null
+#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;
+ }
+}
-#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
{
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 );
--- /dev/null
+#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
-#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 */
float fm; /* LFO modulation depth (+/- hz) */
};
-struct synth_bird{
+struct synth_bird
+{
struct{
int osc_main[4],
osc_lfo;
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 );
--- /dev/null
+#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);
+ }
+}
-#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 );
#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"
};
* 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 );
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 " );
}
/* 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", "" );
}
-#include "vg.h"
-
-#ifdef VG_GAME
- #error !
-#endif
-
#define STB_INCLUDE_IMPLEMENTATION
#define STB_INCLUDE_LINE_GLSL
#include "submodules/stb/stb_include.h"
--- /dev/null
+#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;
+}
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 );
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{
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 );
--- /dev/null
+#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 );
+}
#pragma once
+
#include "vg_m.h"
typedef struct vg_camera vg_camera;
-struct vg_camera{
+struct vg_camera
+{
/* Input */
v3f angles;
v3f pos;
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 );
--- /dev/null
+#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 );
+}
-/* 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__ ;\
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;
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);
--- /dev/null
+#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;
+}
--- /dev/null
+#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"
--- /dev/null
+#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"
+++ /dev/null
-#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 */
--- /dev/null
+#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"
--- /dev/null
+/* 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));
+ }
+}
-/* 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];
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];
};
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) |\
#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,
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,
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);
--- /dev/null
+/* 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;
+}
-/* 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,
vg_normalize
};
-struct{
+struct vg_input
+{
const u8 *sdl_keys;
u32 sdl_mouse;
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 );
--- /dev/null
+/* 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;
+}
-/* 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>
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 );
--- /dev/null
+/* 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;
+ }
+}
-/* 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;
#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;
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);
--- /dev/null
+#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? */
+}
+
/*
- * 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
* -----------------------------------------------------------------------------
*/
-#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{
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 */
-#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 ++;
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 )
* | dwajdkiawjdiw
*/
-#ifdef VG_GAME
- SDL_AtomicLock( &log_print_sl );
+#ifdef VG_ENGINE
+ SDL_AtomicLock( &vg_log.print_sl );
#endif
char buffer[4096];
_vg_log_append_line( logline );
if( location ){
-#ifdef VG_GAME
+#ifdef VG_ENGINE
const char *thread_colours[] = {
KGRN, KMAG, KCYN, KYEL, KBLU
};
}
}
-#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 );
va_end( args );
}
-static void vg_print_backtrace(void){
+void vg_print_backtrace(void)
+{
#ifndef _WIN32
-
void *array[20];
char **strings;
int size, i;
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++ )
#endif
}
-
-#endif /* VG_LOG_C */
-#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
-/* 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
* 6.a Random numbers
*/
-#ifndef VG_M_H
-#define VG_M_H
+#pragma once
#include "vg_platform.h"
#include <math.h>
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;
* -----------------------------------------------------------------------------
*/
-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;
hsv[0] = vg_fractf( hsv[0] * (60.0f/360.0f) );
}
-
-#endif /* VG_M_H */
--- /dev/null
+/* 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) );
+}
--- /dev/null
+#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" );
+ }
+}
-#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
{
#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.
*/
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
#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 );
--- /dev/null
+#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;
+ }
+}
-#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 );
--- /dev/null
+#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);
+}
-#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 );
--- /dev/null
+#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" );
+ }
+ }
+ }
+}
-#ifndef VG_MSG_H
-#define VG_MSG_H
-#include "vg_stdint.h"
-#include "vg_platform.h"
-
+#pragma once
/*
* Example data:
* kvstr "someinfo"
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,
error;
};
-struct vg_msg_cmd{
+struct vg_msg_cmd
+{
u8 code;
const char *key;
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 );
--- /dev/null
+/*
+ * 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;
+}
/*
- * 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);
--- /dev/null
+#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;
+}
-#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 );
-#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;
};
#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>
#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]))
--- /dev/null
+#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 );
+}
-#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
{
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);
--- /dev/null
+#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 );
+}
#pragma once
-
/*
* Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
*
* 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,
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;
/*
* 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 );
--- /dev/null
+#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 );
+ }
+}
#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];
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 );
--- /dev/null
+#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
+ }
+ }
+}
#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 */
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 );
--- /dev/null
+#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)) );
+}
#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 );
+++ /dev/null
-#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 */
--- /dev/null
+/* 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;
+}
-/* 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
{
* 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 );
+++ /dev/null
-#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 */
--- /dev/null
+#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 );
+}
-#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];
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 );
--- /dev/null
+#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);
-#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 )
typedef void ISteamUser;
ISteamUser *SteamAPI_SteamUser_v021(void);
-ISteamUser *SteamAPI_SteamUser(void)
+static inline ISteamUser *SteamAPI_SteamUser(void)
{
return SteamAPI_SteamUser_v021();
}
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';
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;
return 0;
}
}
-
-
-#endif /* VG_STEAM_AUTH_H */
-#ifndef VG_STEAM_FRIENDS_H
-#define VG_STEAM_FRIENDS_H
-
+#pragma once
#include "vg_steam.h"
#if defined( VALVE_CALLBACK_PACK_SMALL )
typedef void ISteamFriends;
ISteamFriends *SteamAPI_SteamFriends_v017(void);
-ISteamFriends *SteamAPI_SteamFriends(void)
+static inline ISteamFriends *SteamAPI_SteamFriends(void)
{
return SteamAPI_SteamFriends_v017();
}
steamapi_bool SteamAPI_ISteamFriends_HasFriend( ISteamFriends* self,
u64_steamid steamIDFriend, int iFriendFlags );
-
-#endif /* VG_STEAM_FRIENDS_H */
-#ifndef VG_STEAM_HTTP_H
-#define VG_STEAM_HTTP_H
-
+#pragma once
#include "vg_steam.h"
/*
*/
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();
}
steamapi_bool SteamAPI_ISteamHTTP_GetHTTPResponseBodyData( void* self,
HTTPRequestHandle hRequest, u8 *pBodyDataBuffer, u32 unBufferSize );
-
-#endif /* VG_STEAM_HTTP_H */
-#ifndef VG_STEAM_NETWORKING_H
-#define VG_STEAM_NETWORKING_H
-
+#pragma once
#include "vg_steam.h"
#ifdef VALVE_CALLBACK_PACK_SMALL
ISteamNetworkingSockets
*SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012(void);
-ISteamNetworkingSockets
+static inline ISteamNetworkingSockets
*SteamAPI_SteamGameServerNetworkingSockets_SteamAPI(void)
{
return SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012();
ISteamNetworkingSockets *SteamAPI_SteamNetworkingSockets_SteamAPI_v012();
+static inline
ISteamNetworkingSockets *SteamAPI_SteamNetworkingSockets_SteamAPI()
{
return SteamAPI_SteamNetworkingSockets_SteamAPI_v012();
* Utility
*/
-static const char *string_ESteamNetworkingConnectionState(
+static inline const char *string_ESteamNetworkingConnectionState(
ESteamNetworkingConnectionState s )
{
switch(s)
return "Error";
}
-static const char *string_ESteamNetworkingAvailability(
+static inline const char *string_ESteamNetworkingAvailability(
ESteamNetworkingAvailability s )
{
switch(s)
break;
}
}
-
-#endif /* VG_STEAM_NETWORKING_H */
-#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;
#pragma pack( pop )
ISteamRemoteStorage *SteamAPI_SteamRemoteStorage_v016();
-ISteamRemoteStorage *SteamAPI_SteamRemoteStorage(){
+static inline ISteamRemoteStorage *SteamAPI_SteamRemoteStorage()
+{
return SteamAPI_SteamRemoteStorage_v016();
}
ISteamRemoteStorage *self );
steamapi_bool SteamAPI_ISteamRemoteStorage_EndFileWriteBatch(
ISteamRemoteStorage *self );
-
-#endif /* VG_STEAM_REMOTE_STORAGE_H */
-#ifndef VG_STEAM_UGC_H
-#define VG_STEAM_UGC_H
-
+#pragma once
#include "vg_steam.h"
#include "vg_steam_remote_storage.h"
#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();
}
steamapi_bool SteamAPI_ISteamUGC_ShowWorkshopEULA( ISteamUGC* self );
SteamAPICall_t SteamAPI_ISteamUGC_GetWorkshopEULAStatus( ISteamUGC* self );
-
-#endif /* VG_STEAM_UGC_H */
-#ifndef VG_STEAM_USER_STATS_H
-#define VG_STEAM_USER_STATS_H
-
+#pragma once
#include "vg_steam.h"
#if defined( VALVE_CALLBACK_PACK_SMALL )
typedef void ISteamUserStats;
ISteamUserStats *SteamAPI_SteamUserStats_v012(void);
-ISteamUserStats *SteamAPI_SteamUserStats(void)
+static inline ISteamUserStats *SteamAPI_SteamUserStats(void)
{
return SteamAPI_SteamUserStats_v012();
}
SteamAPI_ISteamUserStats_ClearAchievement( ISteamUserStats *self,
const char *pchName );
steamapi_bool SteamAPI_ISteamUserStats_StoreStats( ISteamUserStats* self );
-
-#endif /* VG_STEAM_USER_STATS_H */
-#ifndef VG_STEAM_UTILS_H
-#define VG_STEAM_UTILS_H
-
+#pragma once
#include "vg_steam.h"
#if defined( VALVE_CALLBACK_PACK_SMALL )
typedef void ISteamInput;
ISteamUtils *SteamAPI_SteamUtils_v010(void);
-ISteamUtils *SteamAPI_SteamUtils(void)
+static inline ISteamUtils *SteamAPI_SteamUtils(void)
{
return SteamAPI_SteamUtils_v010();
}
ISteamInput *SteamAPI_SteamInput_v006(void);
-ISteamInput *SteamAPI_SteamInput(void)
+static inline ISteamInput *SteamAPI_SteamInput(void)
{
return SteamAPI_SteamInput_v006();
}
ISteamInput* self, InputHandle_t inputHandle );
void SteamAPI_ISteamInput_RunFrame( ISteamInput* self,
steamapi_bool bReservedValue );
-
-
-#endif /* VG_STEAM_UTILS_H */
+++ /dev/null
-#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 */
--- /dev/null
+#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;
+}
-#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 )
--- /dev/null
+#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 );
+}
-/* 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
{
#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 );
--- /dev/null
+#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);
+}
--- /dev/null
+#pragma once
+#include "vg_log.h"
+void vg_fatal_error( const char *fmt, ... );
--- /dev/null
+#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 );