X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=fishladder.c;h=de9da68cac28ba2d7dc9acff593111af255e80ae;hb=e750e1ea996212bec83072cab19cc21c358b4d89;hp=3bcd9af194be3d324c2dfa10b6c3af01ce5e1162;hpb=c4e640773aac3e864dc45b86f22ee21715119a7d;p=fishladder.git diff --git a/fishladder.c b/fishladder.c index 3bcd9af..de9da68 100644 --- a/fishladder.c +++ b/fishladder.c @@ -2,89 +2,83 @@ //#define VG_STEAM #include "vg/vg.h" +#include "fishladder_resources.h" -SHADER_DEFINE( shader_tile_colour, - - // VERTEX - "layout (location=0) in vec2 a_co;" - "uniform mat3 uPv;" - "uniform vec3 uOffset;" - "" - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co * uOffset.z + uOffset.xy, 1.0 ), 1.0 );" - "}", - - // FRAGMENT - "out vec4 FragColor;" - "uniform vec4 uColour;" - "" - "void main()" - "{" - "FragColor = uColour;" - "}" - , - UNIFORMS({ "uPv", "uOffset", "uColour" }) -) - -const char *level_pack[] = -{ - // Level 0 - "#########;\n" - "###-#####;acac\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "#####+###;acac\n" - "#########;\n", - - // Level 1 - "#########;\n" - "##-###-##;b,b\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "####+####;bb\n" - "#########;\n", - - // Level 2 - "###########;\n" - "#####-#####;bbbbb\n" - "## ##;\n" - "## ###;\n" - "## # ##;\n" - "## ##;\n" - "###+##+####;bbb,bb\n" - "###########;\n", - - // Level 3 - "#############;\n" - "###-#####-###;a,aaa\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "######+######;aaaa\n" - "#############;\n", - - // Level 4 - "#############;\n" - "###-#####-###;aaa,aa\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "###+#####+###;aa,aaa\n" - "#############;\n" +const char *level_pack_1[] = { + "level0", + "level1", + "level2", + "level3", + "level4", + "level5" }; +#pragma pack(push,1) +struct career_state +{ + u32 version; + + struct career_level + { + u32 score; + u32 time; + u32 completed; + } + levels[ vg_list_size( level_pack_1 ) ]; +} +career = { .version = 1 }; +#pragma pack(pop) + +static void career_serialize(void) +{ + vg_asset_write( "sav/game.sav", &career, sizeof( struct career_state ) ); +} + +static void career_load(void) +{ + i64 sz; + struct career_state *cr = vg_asset_read_s( "sav/game.sav", &sz ); + + memset( (void*)career.levels, 0, vg_list_size(level_pack_1) * sizeof(struct career_level) ); + + if( cr ) + { + if( sz > sizeof( struct career_state ) ) + vg_warn( "This save file is too big! Some levels will be lost\n" ); + + if( sz <= offsetof( struct career_state, levels ) ) + { + vg_error( "This save file is too small to have a header\n" ); + free( cr ); + return; + } + + u32 const size_header = offsetof(struct career_state, levels); + u32 const size_levels = sizeof(struct career_state)-size_header; + u32 const size_levels_input = sz - size_header; + + memcpy( (void*)career.levels, (void*)cr->levels, size_levels ); + + if( sz < sizeof( struct career_state ) ) + { + memset( ((void*)career.levels) + size_levels_input, 0, size_levels-size_levels_input ); + } + + free( cr ); + vg_success( "Loaded save file... Info:\n" ); + + for( int i = 0; i < vg_list_size( career.levels ); i ++ ) + { + struct career_level *lvl = &career.levels[i]; + vg_info( "Score: %u, Time: %u, Completed: %u\n", lvl->score, lvl->time, lvl->completed ); + } + } + else + { + vg_info( "No save file... Using blank one\n" ); + } +} + m3x3f m_projection; m3x3f m_view; m3x3f m_mdl; @@ -93,16 +87,40 @@ m3x3f m_mdl; #define FLAG_OUTPUT 0x2 #define FLAG_CANAL 0x4 #define FLAG_WALL 0x8 -#define FLAG_DROP_L 0x10 -#define FLAG_SPLIT 0x20 -#define FLAG_MERGER 0x40 -#define FLAG_DROP_R 0x80 #define FLAG_FLIP_FLOP 0x100 +#define FLAG_FLIP_ROTATING 0x200 + +/* + 0000 0 | 0001 1 | 0010 2 | 0011 3 + | | | | | + X | X= | X | X= + | | | + 0100 4 | 0101 5 | 0110 6 | 0111 7 + | | | | | + =X | =X= | =X | =X= + | | | + 1000 8 | 1001 9 | 1010 10 | 1011 11 + | | | | | + X | X= | X | X= + | | | | | | | + 1100 12 | 1101 13 | 1110 14 | 1111 15 + | | | | | + =X | =X= | =X | =X= + | | | | | | | +*/ + +enum cell_type +{ + k_cell_type_ramp_right = 3, + k_cell_type_ramp_left = 6, + k_cell_type_split = 7, + k_cell_type_merge = 13 +}; v3f colour_sets[] = -{ { 0.9f, 0.2f, 0.01f }, +{ { 0.9f, 0.6f, 0.20f }, { 0.2f, 0.9f, 0.14f }, - { 0.1f, 0.3f, 0.85f } }; + { 0.4f, 0.8f, 1.00f } }; static void colour_code_v3( char cc, v3f target ) { @@ -126,7 +144,7 @@ struct mesh u32 elements; }; -static void init_mesh( struct mesh *m, float *tris, u32 length ) +static void init_mesh( struct mesh *m, float const *tris, u32 length ) { m->elements = length/3; glGenVertexArrays( 1, &m->vao ); @@ -173,6 +191,8 @@ struct world float sim_start; int simulating; + float frame_lerp; + struct cell_terminal { // TODO: Split into input/output structures @@ -185,9 +205,13 @@ struct world u32 w, h; - struct mesh tile, circle, splitter_l, splitter_r; + struct mesh tile, circle, numbers; + + GLuint background_data; + GLuint random_samples; - int selected; + int selected, tile_x, tile_y; + v2f tile_pos; struct fish { @@ -195,10 +219,21 @@ struct world v2i dir; int alive; char payload; + float death_time; + v2f physics_v; + v2f physics_co; } fishes[16]; int num_fishes; + + char map_name[128]; + struct career_level *ptr_career_level; + + u32 score; + u32 completed; + u32 time; + } world = {}; static void map_free(void) @@ -213,9 +248,13 @@ static void map_free(void) world.h = 0; world.data = NULL; world.io = NULL; + world.score = 0; + world.time = 0; + world.completed = 0; } -static int map_load( const char *str ) +static void map_reclassify( v2i start, v2i end, int update_texbuffer ); +static int map_load( const char *str, const char *name ) { map_free(); @@ -310,28 +349,45 @@ static int map_load( const char *str ) // Tile initialization // row[ cx ] .. etc + struct cell *cell = &row[ cx ]; if( *c == '+' || *c == '-' ) { struct cell_terminal term = { .id = cx + world.h*world.w }; arrpush( world.io, term ); - row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT; + cell->state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT; reg_end ++; } - else if( *c == '#' ) - { - row[ cx ++ ].state = FLAG_WALL; - } - else - { - row[ cx ++ ].state = 0x00; - } + else if( *c == '#' ) cell->state = FLAG_WALL; + else if( *c == '*' ) { cell->state = FLAG_CANAL; world.score ++; } + else cell->state = 0x00; + + cx ++; } c ++; } - vg_success( "Map loaded! (%u:%u)\n", world.w, world.h ); + // Update data texture to fill out the background + { + u8 info_buffer[64*64*4]; + for( int i = 0; i < 64*64; i ++ ) + { + u8 *px = &info_buffer[i*4]; + px[0] = 255; + px[1] = 0; + px[2] = 0; + px[3] = 0; + } + + glBindTexture( GL_TEXTURE_2D, world.background_data ); + glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer ); + } + + map_reclassify( NULL, NULL, 1 ); + vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h ); + + strncpy( world.map_name, name, vg_list_size( world.map_name )-1 ); return 1; } @@ -340,18 +396,179 @@ static struct cell *pcell( v2i pos ) return &world.data[ pos[1]*world.w + pos[0] ]; } +static void map_serialize( FILE *stream ) +{ + for( int y = 0; y < world.h; y ++ ) + { + for( int x = 0; x < world.w; x ++ ) + { + struct cell *cell = pcell( (v2i){ x, y } ); + + if( cell->state & FLAG_WALL ) fputc( '#', stream ); + else if( cell->state & FLAG_INPUT ) fputc( '+', stream ); + else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream ); + else if( cell->state & FLAG_CANAL ) fputc( '*', stream ); + else fputc( ' ', stream ); + } + + fputc( ';', stream ); + + int terminal_write_count = 0; + + for( int x = 0; x < world.w; x ++ ) + { + for( int i = 0; i < arrlen( world.io ); i ++ ) + { + struct cell_terminal *term = &world.io[i]; + if( term->id == y*world.w+x ) + { + if( terminal_write_count ) + fputc( ',', stream ); + terminal_write_count ++; + + for( int j = 0; j < arrlen( term->conditions ); j ++ ) + fputc( term->conditions[j], stream ); + } + } + } + + fputc( '\n', stream ); + } +} + int main( int argc, char *argv[] ) { vg_init( argc, argv, "Fish (Marbles Computer) Ladder Simulator 2022 | N,M: change level | SPACE: Test | LeftClick: Toggle tile" ); } -void vg_register(void) +static int console_save_map( int argc, char const *argv[] ) +{ + char map_path[ 256 ]; + + strcpy( map_path, "sav/" ); + strcat( map_path, world.map_name ); + strcat( map_path, ".map" ); + + FILE *test_writer = fopen( map_path, "wb" ); + if( test_writer ) + { + map_serialize( test_writer ); + + fclose( test_writer ); + return 1; + } + else + { + vg_error( "Unable to open stream for writing\n" ); + return 0; + } +} + +static int console_load_map( int argc, char const *argv[] ) +{ + char map_path[ 256 ]; + + if( argc >= 1 ) + { + // try from saves + strcpy( map_path, "sav/" ); + strcat( map_path, argv[0] ); + strcat( map_path, ".map" ); + + char *text_source = vg_textasset_read( map_path ); + + if( !text_source ) + { + strcpy( map_path, "maps/" ); + strcat( map_path, argv[0] ); + strcat( map_path, ".map" ); + + text_source = vg_textasset_read( map_path ); + } + + if( text_source ) + { + map_load( text_source, argv[0] ); + free( text_source ); + + // Update career link + world.ptr_career_level = NULL; + + for( int i = 0; i < vg_list_size( level_pack_1 ); i ++ ) + { + if( !strcmp( level_pack_1[i], argv[0] ) ) + { + world.ptr_career_level = career.levels + i; + break; + } + } + + return 1; + } + else + { + vg_error( "Missing maps '%s'\n", argv[0] ); + return 0; + } + } + else + { + vg_error( "Missing argument \n" ); + return 0; + } +} + +static void simulation_stop(void) +{ + world.simulating = 0; + world.num_fishes = 0; + world.sim_frame = 0; + + for( int i = 0; i < arrlen( world.io ); i ++ ) + world.io[i].recv_count = 0; + + sfx_system_fadeout( &audio_system_balls_rolling, 44100 ); + + vg_info( "Stopping simulation!\n" ); +} + +static int console_changelevel( int argc, char const *argv[] ) { - SHADER_INIT( shader_tile_colour ); + if( argc >= 1 ) + { + // Save current level + if( console_save_map( 0, NULL ) ) + if( console_load_map( argc, argv ) ) + { + simulation_stop(); + return 1; + } + } + else + { + vg_error( "Missing argument \n" ); + } + + return 0; } void vg_start(void) { + vg_function_push( (struct vg_cmd){ + .name = "map_write", + .function = console_save_map + }); + + vg_function_push( (struct vg_cmd){ + .name = "map_load", + .function = console_load_map + }); + + vg_function_push( (struct vg_cmd){ + .name = "changelevel", + .function = console_changelevel + }); + // Quad mesh { float quad_mesh[] = @@ -359,11 +576,8 @@ void vg_start(void) 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, - 0.48f, 0.48f, 0.5f, 0.52f, 0.52f, 0.52f, // Static dot - 0.375f, 0.25f, 0.5f, 0.75f, 0.625f, 0.25f, // Downwards pointing arrow - 0.25f, 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, // Left - 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, 0.75f, // up - 0.75f, 0.375f, 0.25f, 0.5f, 0.75f, 0.625f + 0.0f, 0.0f, 0.0f, 1.0f, 4.0f, 1.0f, + 0.0f, 0.0f, 4.0f, 1.0f, 4.0f, 0.0f }; init_mesh( &world.tile, quad_mesh, vg_list_size(quad_mesh) ); @@ -399,35 +613,64 @@ void vg_start(void) init_mesh( &world.circle, circle_mesh, vg_list_size( circle_mesh ) ); } - // splitters (temp) + // Numbers mesh { - float splitter_l[] = - { - #include "models/splitter_l.obj.h" - }; - float splitter_r[] = + init_mesh( &world.numbers, + MESH_NUMBERS_BUFFER, + vg_list_size( MESH_NUMBERS_BUFFER ) + ); + + for( int i = 0; i < 10; i ++ ) { - #include "models/splitter_r.obj.h" - }; + vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] ); + } + } + + // Create info data texture + { + glGenTextures( 1, &world.background_data ); + glBindTexture( GL_TEXTURE_2D, world.background_data ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + vg_tex2d_nearest(); + } + + // Create random smaples texture + { + u8 *data = malloc(512*512*2); + for( int i = 0; i < 512*512*2; i ++ ) + data[ i ] = rand()/(RAND_MAX/255); - init_mesh( &world.splitter_l, splitter_l, vg_list_size( splitter_l ) ); - init_mesh( &world.splitter_r, splitter_r, vg_list_size( splitter_r ) ); + glGenTextures( 1, &world.random_samples ); + glBindTexture( GL_TEXTURE_2D, world.random_samples ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data ); + vg_tex2d_linear(); + vg_tex2d_repeat(); + + free( data ); } - map_load( level_pack[ 0 ] ); + resource_load_main(); - ui_test_init(); + // Restore gamestate + career_load(); + console_load_map( 1, level_pack_1 ); } void vg_free(void) { + console_save_map( 0, NULL ); + career_serialize(); + + resource_free_main(); + + glDeleteTextures( 1, &world.background_data ); + glDeleteTextures( 1, &world.random_samples ); + free_mesh( &world.tile ); free_mesh( &world.circle ); - free_mesh( &world.splitter_l ); - free_mesh( &world.splitter_r ); - + free_mesh( &world.numbers ); + map_free(); - ui_test_free(); } static int cell_interactive( v2i co ) @@ -515,85 +758,157 @@ static int cell_interactive( v2i co ) return 1; } -void vg_update(void) +static void map_reclassify( v2i start, v2i end, int update_texbuffer ) { - static int curlevel = 0; - int changelvl = curlevel; - if( vg_get_button_down( "prev" ) ) { if( curlevel > 0 ) changelvl --; } - else if( vg_get_button_down( "next" ) ) { if( curlevel < vg_list_size( level_pack )-1 ) changelvl ++; } + v2i full_start = { 1,1 }; + v2i full_end = { world.w-1, world.h-1 }; - if( changelvl != curlevel ) + if( !start || !end ) { - map_load( level_pack[ changelvl ] ); - curlevel = changelvl; - - // TEMP!!! code dupe - world.simulating = 0; - world.num_fishes = 0; - world.sim_frame = 0; - - for( int i = 0; i < arrlen( world.io ); i ++ ) - world.io[i].recv_count = 0; + start = full_start; + end = full_end; + } + + // Texture data + u8 info_buffer[64*64*4]; + u32 pixel_id = 0; + + int px0 = vg_max( start[0], full_start[0] ), + px1 = vg_min( end[0], full_end[0] ), + py0 = vg_max( start[1], full_start[1] ), + py1 = vg_min( end[1], full_end[1] ); + + for( int y = py0; y < py1; y ++ ) + { + for( int x = px0; x < px1; x ++ ) + { + struct cell *cell = pcell((v2i){x,y}); - vg_info( "Stopping simulation!\n" ); + v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}}; + + u8 height = 0; + u8 config = 0x00; + + if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) ) + { + for( int i = 0; i < vg_list_size( dirs ); i ++ ) + { + struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]}); + if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) ) + config |= 0x1 << i; + } + + height = 128; + } + else + { + if( cell->state & FLAG_WALL ) + height = 255; + + config = 0xF; + } + + pcell((v2i){x,y})->config = config; + + u8 *info_px = &info_buffer[ (pixel_id ++)*4 ]; + info_px[0] = height; + info_px[1] = cell->state & FLAG_WALL? 0: 255; + info_px[2] = 0; + info_px[3] = 0; + } + } + + if( update_texbuffer ) + { + glBindTexture( GL_TEXTURE_2D, world.background_data ); + glTexSubImage2D( GL_TEXTURE_2D, 0, px0 + 16, py0 + 16, px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer ); } +} + +v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}}; +v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}}; +v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}}; +v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}}; + +v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}}; +v2f const curve_8[] = {{0.5f,0.8f},{0.5f,0.5f},{0.5f,0.3f},{0.5f,0.0f}}; + +v2f const curve_7[] = {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}}; +v2f const curve_7_1[] = {{0.5f,0.8438f},{1.0f-0.875f,0.8438f},{1.0-0.625f,0.5f},{0.0f,0.5f}}; + +float const curve_7_linear_section = 0.1562f; + +void vg_update(void) +{ + // Fit within screen + + float r1 = (float)vg_window_y / (float)vg_window_x, + r2 = (float)world.h / (float)world.w, + size; - float ratio = (float)vg_window_y / (float)vg_window_x; - float const size = 9.5f; + size = ( r2 < r1? (float)world.w * 0.5f: ((float)world.h * 0.5f) / r1 ) + 2.5f; + m3x3_projection( m_projection, -size, size, -size*r1, size*r1 ); v3f origin; - origin[0] = -0.5f * world.w; - origin[1] = -0.5f * world.h; + origin[0] = floorf( -0.5f * world.w ); + origin[1] = floorf( -0.5f * world.h ); origin[2] = 0.0f; - m3x3_projection( m_projection, -size, size, -size*ratio, size*ratio ); m3x3_identity( m_view ); m3x3_translate( m_view, origin ); m3x3_mul( m_projection, m_view, vg_pv ); vg_projection_update(); // Input stuff + v2_copy( vg_mouse_ws, world.tile_pos ); - v2f tile_pos; - v2_copy( vg_mouse_ws, tile_pos ); - - int tile_x = floorf( tile_pos[0] ); - int tile_y = floorf( tile_pos[1] ); + world.tile_x = floorf( world.tile_pos[0] ); + world.tile_y = floorf( world.tile_pos[1] ); // Tilemap editing if( !world.simulating ) { - if( cell_interactive( (v2i){ tile_x, tile_y } )) + if( cell_interactive( (v2i){ world.tile_x, world.tile_y } )) { - world.selected = tile_y * world.w + tile_x; + world.selected = world.tile_y * world.w + world.tile_x; if( vg_get_button_down("primary") ) { world.data[ world.selected ].state ^= FLAG_CANAL; + + if( world.data[ world.selected ].state & FLAG_CANAL ) + { + sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 ); + world.score ++; + } + else + { + sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 ); + world.score --; + } + + map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 }, + (v2i){ world.tile_x +2, world.tile_y +2 }, 1 ); } } else world.selected = -1; } + else world.selected = -1; // Simulation stop/start if( vg_get_button_down("go") ) { if( world.simulating ) { - world.simulating = 0; - world.num_fishes = 0; - world.sim_frame = 0; - - for( int i = 0; i < arrlen( world.io ); i ++ ) - world.io[i].recv_count = 0; - - vg_info( "Stopping simulation!\n" ); + simulation_stop(); } else { vg_success( "Starting simulation!\n" ); + sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 ); + world.simulating = 1; world.num_fishes = 0; world.sim_frame = 0; @@ -608,70 +923,159 @@ void vg_update(void) world.io[i].recv_count = 0; } } - - // Simulation stuff - // ======================================================== - - // Reclassify world. TODO: Move into own function - for( int y = 2; y < world.h-2; y ++ ) - { - for( int x = 2; x < world.w-2; x ++ ) + + // Fish ticks + if( world.simulating ) + { + while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) ) { - v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}}; - - u8 config = 0x00; - - if( pcell((v2i){x,y})->state & FLAG_CANAL ) + //vg_info( "frame: %u\n", world.sim_frame ); + sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 9 ); + + // Update splitter deltas + for( int i = 0; i < world.h*world.w; i ++ ) { - for( int i = 0; i < vg_list_size( dirs ); i ++ ) + struct cell *cell = &world.data[i]; + if( cell->config == k_cell_type_split ) { - struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]}); - if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) ) - config |= 0x1 << i; + cell->state &= ~FLAG_FLIP_ROTATING; } - } else config = 0xF; - - pcell((v2i){x,y})->config = config; - pcell((v2i){x,y})->state &= ~(FLAG_DROP_L|FLAG_DROP_R|FLAG_SPLIT|FLAG_MERGER); - } - } - - for( int y = 2; y < world.h-2; y ++ ) - for( int x = 2; x < world.w-2; x ++ ) - { - // R,D,L,- 1110 (splitter, 1 drop created) - - // R,-,L,U - 1011 (merger, 2 drop created) - - u8 config = pcell((v2i){x,y})->config; - - if( config == 0x7 ) // splitter - { - world.data[y*world.w+x ].state |= (FLAG_SPLIT | FLAG_DROP_L | FLAG_DROP_R); - } - else if( config == 0xD ) - { - world.data[y*world.w+x-1].state |= FLAG_DROP_R; - world.data[y*world.w+x+1].state |= FLAG_DROP_L; - world.data[y*world.w+x].state |= FLAG_MERGER; } - } - - // Fish ticks - if( world.simulating ) - { - while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) ) - { - vg_info( "frame: %u\n", world.sim_frame ); - for( int i = 0; i < arrlen( world.io ); i ++ ) + int alive_count = 0; + + // Update fish positions + for( int i = 0; i < world.num_fishes; i ++ ) { - struct cell_terminal *term = &world.io[ i ]; - int posx = term->id % world.w; - int posy = (term->id - posx)/world.w; - int is_input = world.data[ term->id ].state & FLAG_INPUT; + struct fish *fish = &world.fishes[i]; + struct cell *cell_current = pcell( fish->pos ); - if( is_input ) + if( fish->alive == -1 ) + fish->alive = 0; + + if( fish->alive != 1 ) + continue; + + // Apply to output + if( cell_current->state & FLAG_OUTPUT ) + { + for( int j = 0; j < arrlen( world.io ); j ++ ) + { + struct cell_terminal *term = &world.io[j]; + + if( term->id == fish->pos[1]*world.w + fish->pos[0] ) + { + term->recv[ term->recv_count ++ ] = fish->payload; + break; + } + } + + fish->alive = 0; + continue; + } + + if( cell_current->config == k_cell_type_split ) + { + // Flip flop L/R + fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1; + fish->dir[1] = 0; + + cell_current->state ^= FLAG_FLIP_FLOP; + } + else if( cell_current->config == k_cell_type_merge ) + { + // Can only move up + fish->dir[0] = 0; + fish->dir[1] = -1; + } + else + { + struct cell *cell_next = pcell( (v2i){ fish->pos[0]+fish->dir[0], fish->pos[1]+fish->dir[1] } ); + if( !(cell_next->state & (FLAG_CANAL|FLAG_OUTPUT)) ) + { + // Try other directions for valid, so down, left, right.. + v2i dirs[] = {{1,0},{-1,0},{0,-1}}; + vg_info( "Trying some other directions...\n" ); + + for( int j = 0; j < vg_list_size(dirs); j ++ ) + { + if( (dirs[j][0] == -fish->dir[0]) && (dirs[j][1] == -fish->dir[1]) ) + continue; + + if( pcell( (v2i){ fish->pos[0]+dirs[j][0], fish->pos[1]+dirs[j][1] } )->state & (FLAG_CANAL|FLAG_OUTPUT) ) + { + fish->dir[0] = dirs[j][0]; + fish->dir[1] = dirs[j][1]; + } + } + } + } + + fish->pos[0] += fish->dir[0]; + fish->pos[1] += fish->dir[1]; + + struct cell *cell_entry = pcell( fish->pos ); + + if( !(cell_entry->state & (FLAG_INPUT|FLAG_CANAL|FLAG_OUTPUT) )) + fish->alive = 0; + else + { + if( fish->dir[0] ) + { + if( cell_entry->config == k_cell_type_split || + cell_entry->config == k_cell_type_ramp_right || + cell_entry->config == k_cell_type_ramp_left ) + { + // Special death (FALL) + v2_sub( fish->physics_co, fish->physics_v, fish->physics_v ); + v2_divs( fish->physics_v, vg_time_delta, fish->physics_v ); + + fish->alive = -2; + vg_warn( "Special death (fall)\n" ); + continue; + } + } + + if( cell_entry->config == k_cell_type_split ) + { + sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 ); + cell_entry->state |= FLAG_FLIP_ROTATING; + } + } + + if( fish->alive ) + alive_count ++; + } + + // Check for collisions + for( int i = 0; i < world.num_fishes; i ++ ) + { + if( world.fishes[i].alive == 1 ) + { + for( int j = i+1; j < world.num_fishes; j ++ ) + { + if( (world.fishes[j].alive == 1) && (world.fishes[i].pos[0] == world.fishes[j].pos[0]) && + (world.fishes[i].pos[1] == world.fishes[j].pos[1]) ) + { + // Shatter death (+0.5s) + world.fishes[i].alive = -1; + world.fishes[j].alive = -1; + world.fishes[i].death_time = 0.5f; + world.fishes[j].death_time = 0.5f; + } + } + } + } + + // Spawn fishes + for( int i = 0; i < arrlen( world.io ); i ++ ) + { + struct cell_terminal *term = &world.io[ i ]; + int posx = term->id % world.w; + int posy = (term->id - posx)/world.w; + int is_input = world.data[ term->id ].state & FLAG_INPUT; + + if( is_input ) { if( world.sim_frame < arrlen( term->conditions ) ) { @@ -695,167 +1099,405 @@ void vg_update(void) if( !can_spawn ) world.num_fishes--; + else + alive_count ++; } } } - - for( int i = 0; i < world.num_fishes; i ++ ) + + if( alive_count == 0 ) { - struct fish *fish = &world.fishes[i]; - struct cell *cell_current = pcell( fish->pos ); - - if( !fish->alive ) - continue; - - // Apply to output - if( cell_current->state & FLAG_OUTPUT ) + world.completed = 1; + + for( int i = 0; i < arrlen( world.io ); i ++ ) { - for( int j = 0; j < arrlen( world.io ); j ++ ) + struct cell_terminal *term = &world.io[ i ]; + int is_input = world.data[ term->id ].state & FLAG_INPUT; + + if( !is_input ) { - struct cell_terminal *term = &world.io[j]; - - if( term->id == fish->pos[1]*world.w + fish->pos[0] ) + if( term->recv_count == arrlen( term->conditions ) ) { - term->recv[ term->recv_count ++ ] = fish->payload; + for( int j = 0; j < arrlen( term->conditions ); j ++ ) + { + if( term->recv[j] != term->conditions[j] ) + { + world.completed = 0; + break; + } + } + } + else + { + world.completed = 0; break; } } - - fish->alive = 0; - continue; } - if( !(cell_current->state & (FLAG_INPUT|FLAG_CANAL)) ) + if( world.completed ) { - fish->alive = 0; + vg_success( "Level passed!\n" ); + + u32 score = 0; + for( int i = 0; i < world.w*world.h; i ++ ) + if( world.data[ i ].state & FLAG_CANAL ) + score ++; + + world.score = score; + world.time = world.sim_frame; } else { - if( cell_current->state & FLAG_SPLIT ) - { - // Flip flop L/R - fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1; - fish->dir[1] = 0; - - cell_current->state ^= FLAG_FLIP_FLOP; - } - else if( cell_current->state & FLAG_MERGER ) - { - // Can only move up - fish->dir[0] = 0; - fish->dir[1] = -1; - } - else - { - struct cell *cell_next = pcell( (v2i){ fish->pos[0]+fish->dir[0], fish->pos[1]+fish->dir[1] } ); - if( !(cell_next->state & (FLAG_CANAL|FLAG_OUTPUT)) ) + vg_error( "Level failed :(\n" ); + } + + // Copy into career data + if( world.ptr_career_level ) + { + world.ptr_career_level->score = world.score; + world.ptr_career_level->time = world.time; + world.ptr_career_level->completed = world.completed; + } + + simulation_stop(); // TODO: Async? + break; + } + + world.sim_frame ++; + } + + float scaled_time = 0.0f; + scaled_time = (vg_time-world.sim_start)*2.0f; + world.frame_lerp = scaled_time - (float)world.sim_frame; + + // Update positions + for( int i = 0; i < world.num_fishes; i ++ ) + { + struct fish *fish = &world.fishes[i]; + + if( fish->alive == 0 ) + continue; + + if( fish->alive == -1 && (world.frame_lerp > fish->death_time) ) + continue; // Todo: particle thing? + + if( fish->alive == -2 ) + { + v2_muladds( fish->physics_v, (v2f){ 0.0, -9.8f }, vg_time_delta, fish->physics_v ); + v2_muladds( fish->physics_co, fish->physics_v, vg_time_delta, fish->physics_co ); + } + else + { + struct cell *cell = pcell(fish->pos); + v2f const *curve; + + float t = world.frame_lerp; + + v2_copy( fish->physics_co, fish->physics_v ); + + switch( cell->config ) + { + case 13: + if( fish->dir[0] == 1 ) + curve = curve_12; + else + curve = curve_9; + break; + case 2: curve = curve_2; break; + case 8: curve = curve_8; break; + case 3: curve = curve_3; break; + case 6: curve = curve_6; break; + case 9: curve = curve_9; break; + case 12: curve = curve_12; break; + case 7: + if( t > curve_7_linear_section ) { - // Try other directions for valid, so down, left, right.. - v2i dirs[] = {{1,0},{-1,0},{0,-1}}; - vg_info( "Trying some other directions...\n" ); - - for( int j = 0; j < vg_list_size(dirs); j ++ ) - { - if( (dirs[j][0] == -fish->dir[0]) && (dirs[j][1] == -fish->dir[1]) ) - continue; + t -= curve_7_linear_section; + t *= (1.0f/(1.0f-curve_7_linear_section)); - if( pcell( (v2i){ fish->pos[0]+dirs[j][0], fish->pos[1]+dirs[j][1] } )->state & (FLAG_CANAL|FLAG_OUTPUT) ) - { - fish->dir[0] = dirs[j][0]; - fish->dir[1] = dirs[j][1]; - } - } + curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1; } - } + else curve = NULL; + break; + default: curve = NULL; break; + } + + if( curve ) + { + float t2 = t * t; + float t3 = t * t * t; + + float cA = 3.0f*t2 - 3.0f*t3; + float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t; + float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f; + + fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0]; + fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1]; + fish->physics_co[0] += (float)fish->pos[0]; + fish->physics_co[1] += (float)fish->pos[1]; + } + else + { + v2f origin; + origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f; + origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f; - fish->pos[0] += fish->dir[0]; - fish->pos[1] += fish->dir[1]; + fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t; + fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t; } } + } + } +} + +static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour ) +{ + v2i full_start = { 0,0 }; + v2i full_end = { world.w, world.h }; + + if( !start || !end ) + { + start = full_start; + end = full_end; + } + + glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour ); + + for( int y = start[1]; y < end[1]; y ++ ) + { + for( int x = start[0]; x < end[0]; x ++ ) + { + struct cell *cell = pcell((v2i){x,y}); + int selected = world.selected == y*world.w + x; - world.sim_frame ++; + int tile_offsets[][2] = + { + {2, 0}, {0, 3}, {0, 2}, {2, 2}, + {1, 0}, {2, 3}, {3, 2}, {1, 3}, + {3, 1}, {0, 1}, {1, 2}, {2, 1}, + {1, 1}, {3, 3}, {2, 1}, {2, 1} + }; + + int uv[2] = { 3, 0 }; + + if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) ) + { + uv[0] = tile_offsets[ cell->config ][0]; + uv[1] = tile_offsets[ cell->config ][1]; + } else continue; + + glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] ); + if( selected ) + { + glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour ); + draw_mesh( 0, 2 ); + glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour ); + } + else + draw_mesh( 0, 2 ); } } } +static void draw_numbers( v3f coord, int number ) +{ + v3f pos; + v3_copy( coord, pos ); + int digits[8]; int i = 0; + + while( number > 0 && i < 8 ) + { + digits[i ++] = number % 10; + number = number / 10; + } + + for( int j = 0; j < i; j ++ ) + { + glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, pos ); + draw_mesh( MESH_NUMBERS_OFFSETS[digits[i-j-1]][0], MESH_NUMBERS_OFFSETS[digits[i-j-1]][1] ); + pos[0] += pos[2] * 0.75f; + } +} + void vg_render(void) { glViewport( 0,0, vg_window_x, vg_window_y ); glDisable( GL_DEPTH_TEST ); - glClearColor( 0.8f, 0.8f, 0.8f, 1.0f ); + glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f}; + v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f}; + + // TILE SET RENDERING + // todo: just slam everything into a mesh... + // when user modifies a tile the neighbours can be easily uploaded to gpu mem + // in ~3 subBuffers + // Currently we're uploading a fair amount of data every frame anyway. + // NOTE: this is for final optimisations ONLY! + // ====================================================================== + use_mesh( &world.tile ); - SHADER_USE( shader_tile_colour ); - glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); - // Shadow layer - glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.5f, 0.5f, 0.5f, 1.0f ); - for( int y = 0; y < world.h; y ++ ) - for( int x = 0; x < world.w; x ++ ) - { - struct cell *cell = pcell((v2i){x,y}); + // Draw background + + if(1){ + + SHADER_USE( shader_background ); + glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); + + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, world.background_data ); + glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 ); + + glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 ); + glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f ); + + glActiveTexture( GL_TEXTURE1 ); + glBindTexture( GL_TEXTURE_2D, world.random_samples ); + glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 ); + + draw_mesh( 0, 2 ); + + } + + + SHADER_USE( shader_tile_main ); + + m2x2f subtransform; + m2x2_identity( subtransform ); + glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform ); + glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); + glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f ); + glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f ); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + + // Bind textures + vg_tex2d_bind( &tex_tile_data, 0 ); + glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 ); + + vg_tex2d_bind( &tex_wood, 1 ); + glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 ); + + render_tiles( NULL, NULL, colour_default, colour_default ); + + + + SHADER_USE( shader_ball ); + glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); + + vg_tex2d_bind( &tex_ball, 0 ); + glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 ); + + // Draw 'fish' + if( world.simulating ) + { + for( int i = 0; i < world.num_fishes; i ++ ) + { + struct fish *fish = &world.fishes[i]; - if( cell->state & FLAG_CANAL ) - { + if( fish->alive == 0 ) continue; - } - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x - 0.2f, (float)y - 0.15f, 1.0f ); - draw_mesh( 0, 2 ); + if( fish->alive == -1 && (world.frame_lerp > fish->death_time) ) + continue; + + v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f }; + colour_code_v3( fish->payload, dot_colour ); + + glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour ); + glUniform2fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, fish->physics_co ); + draw_mesh( 0, 32 ); } + } - for( int y = 0; y < world.h; y ++ ) + SHADER_USE( shader_tile_main ); + + // Bind textures + vg_tex2d_bind( &tex_tile_data, 0 ); + glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 ); + + vg_tex2d_bind( &tex_wood, 1 ); + glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 ); + + glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f ); + render_tiles( NULL, NULL, colour_default, colour_selected ); + + // Draw splitters + for( int y = 2; y < world.h-2; y ++ ) { - for( int x = 0; x < world.w; x ++ ) + for( int x = 2; x < world.w-2; x ++ ) { struct cell *cell = pcell((v2i){x,y}); - int selected = world.selected == y*world.w + x; - - if( cell->state & FLAG_SPLIT ) + + if( cell->config == k_cell_type_split ) { - glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.9f, 0.9f, 1.0f ); - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y, 1.0f ); - - struct mesh *splitter = cell->state & FLAG_FLIP_FLOP? &world.splitter_r: &world.splitter_l; + float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f ); - use_mesh( splitter ); - draw_mesh( 0, splitter->elements ); - use_mesh( &world.tile ); + if( cell->state & FLAG_FLIP_ROTATING ) + { + if( (world.frame_lerp > curve_7_linear_section) ) + { + float const rotation_speed = 0.4f; + if( (world.frame_lerp < 1.0f-rotation_speed) ) + { + float t = world.frame_lerp - curve_7_linear_section; + t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed))); + t += 1.0f; + + rotation *= t; + } + else + rotation *= -1.0f; + } + } + + m2x2_create_rotation( subtransform, rotation ); + + glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform ); + glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y + 0.125f, 0.0f, 0.0f ); + draw_mesh( 0, 2 ); } + } + } + + // Edit overlay + if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) ) + { + v2i new_begin = { world.tile_x - 2, world.tile_y - 2 }; + v2i new_end = { world.tile_x + 2, world.tile_y + 2 }; + + world.data[ world.selected ].state ^= FLAG_CANAL; + map_reclassify( new_begin, new_end, 0 ); - if( (cell->state & FLAG_CANAL) && !selected ) - continue; + m2x2_identity( subtransform ); + glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f ); + glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform ); + glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos ); - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y, 1.0f ); - - v4f colour; - - if( cell->state & FLAG_WALL ) { v4_copy( (v4f){ 0.2f, 0.2f, 0.2f, 1.0f }, colour ); } - else if( cell->state & FLAG_CANAL ) { v4_copy( (v4f){ 0.6f, 0.6f, 0.6f, 1.0f }, colour ); } - else if( cell->state & FLAG_INPUT ) { v4_copy( (v4f){ 0.5f, 0.5f, 0.5f, 1.0f }, colour ); } - else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.4f, 0.4f, 0.4f, 1.0f }, colour ); } - else v4_copy( (v4f){ 0.9f, 0.9f, 0.9f, 1.0f }, colour ); - - //if( cell->water[world.frame&0x1] ) - // v4_copy( (v4f){ 0.2f, 0.3f, 0.7f * (float)(cell->water[world.frame&0x1]) * (1.0f/16.0f), 1.0f }, colour ); - - if( selected ) - v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour ); - - //if( cell->state & (FLAG_SPLIT) ) - // v4_copy( (v4f){ 0.75f, 0.75f, 0.02f, 1.0f }, colour ); - //if( cell->state & (FLAG_MERGER) ) - // v4_copy( (v4f){ 0.75f, 0.02f, 0.75f, 1.0f }, colour ); - - glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour ); - - draw_mesh( 0, 2 ); - } + render_tiles( new_begin, new_end, colour_default, colour_default ); + + world.data[ world.selected ].state ^= FLAG_CANAL; + map_reclassify( new_begin, new_end, 0 ); } + //glDisable(GL_BLEND); + + glDisable(GL_BLEND); + + SHADER_USE( shader_tile_colour ); + glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); use_mesh( &world.circle ); + + int const filled_start = 0; + int const filled_count = 32; + int const empty_start = 32; + int const empty_count = 32*2; // Draw i/o arrays for( int i = 0; i < arrlen( world.io ); i ++ ) @@ -864,17 +1506,13 @@ void vg_render(void) int posx = term->id % world.w; int posy = (term->id - posx)/world.w; int is_input = world.data[ term->id ].state & FLAG_INPUT; - - int const filled_start = 0; - int const filled_count = 32; - int const empty_start = 32; - int const empty_count = 32*2; - + v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f }; for( int j = 0; j < arrlen( term->conditions ); j ++ ) { - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)posx + 0.2f + 0.2f * (float)j, (float)posy + 0.2f, 0.1f ); + float y_offset = is_input? 1.2f: -0.2f; + glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)posx + 0.2f + 0.2f * (float)j, (float)posy + y_offset, 0.1f ); if( is_input ) { @@ -906,38 +1544,96 @@ void vg_render(void) } } - // Draw 'fish' if( world.simulating ) { - float scaled_time = (vg_time-world.sim_start)*2.0f; - float lerp = 1.0f-(scaled_time - (float)world.sim_frame); + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.0f, 0.0f, 0.0f, 1.0f ); + glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), -0.5f + cosf( vg_time * 4.0f ) * 0.2f, sinf( vg_time * 4.0f ) * 0.2f + (float)world.h * 0.5f, 0.05f ); + draw_mesh( filled_start, filled_count ); + } + + // Draw score + float const score_bright = 1.25f; + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), + 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f ); - v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f }; + use_mesh( &world.numbers ); + draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score ); - for( int i = 0; i < world.num_fishes; i ++ ) + // Level selection UI + use_mesh( &world.circle ); + float ratio = ((float)vg_window_x/(float)vg_window_y); + + m3x3f ui_view = M3X3_IDENTITY; + m3x3_scale( ui_view, (v3f){ 1.0f, ratio, 1.0f } ); + glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)ui_view ); + + // Calculate mouse in UIsp + v3f mouse_ui_space = { ((float)vg_mouse[0] / (float)(vg_window_x)) * 2.0f - 1.0f, + (((float)vg_mouse[1] / (float)(vg_window_y)) * 2.0f - 1.0f)*(-1.0f/ratio), 0.0125f }; + + // Get selected level + const float selection_scale = 0.05f; + int const level_count = vg_list_size( level_pack_1 ); + int level_select = -1; + + if( mouse_ui_space[0] <= -0.8f ) + { + float levels_range = (float)level_count*selection_scale*0.6f; + float level_offset = ((-mouse_ui_space[1] + levels_range) / levels_range) * 0.5f * (float)level_count; + level_select = ceilf( level_offset ); + + // Draw selector + if( level_select >= 0 && level_select < vg_list_size( level_pack_1 ) ) { - struct fish *fish = &world.fishes[i]; + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.369768f, 0.3654f, 0.42f, 1.0f ); - if( !fish->alive ) - continue; + use_mesh( &world.tile ); + glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), + -1.0f, + ((float)level_count - (float)level_select * 2.0f ) * selection_scale * 0.6f, + selection_scale + ); + draw_mesh( 2, 2 ); - colour_code_v3( fish->payload, dot_colour ); - glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour ); + use_mesh( &world.circle ); - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)fish->pos[0] + 0.5f - (float)fish->dir[0]*lerp, (float)fish->pos[1] + 0.25f - (float)fish->dir[1]*lerp, 0.25f ); - draw_mesh( 0, 32 ); + if( vg_get_button_down( "primary" ) ) + { + console_changelevel( 1, level_pack_1 + level_select ); + } } } - - if( world.simulating ) + else mouse_ui_space[1] = INFINITY; + + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f, 0.39f, 0.45f, 1.0f ); + + // Draw levels + for( int i = 0; i < level_count; i ++ ) { - glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.0f, 0.0f, 0.0f, 1.0f ); - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), -0.5f + cosf( vg_time * 4.0f ) * 0.2f, sinf( vg_time * 4.0f ) * 0.2f + (float)world.h * 0.5f, 0.05f ); - draw_mesh( 0, 32 ); + struct career_level *clevel = &career.levels[i]; + + v3f level_ui_space = { + -0.97f, + ((float)level_count - (float)i * 2.0f ) * selection_scale * 0.6f + selection_scale * 0.5f, + selection_scale * 0.5f + }; + + float scale = vg_clampf( 1.0f - fabsf(level_ui_space[1] - mouse_ui_space[1]) * 2.0f, 0.9f, 1.0f ); + level_ui_space[2] *= scale; + + glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, level_ui_space ); + + if( clevel->completed ) + draw_mesh( filled_start, filled_count ); + else + draw_mesh( empty_start, empty_count ); } + + //use_mesh( &world.numbers ); + //draw_numbers( (v3f){ 0.0f, -0.5f, 0.1f }, 128765 ); } void vg_ui(void) { - ui_test(); + //ui_test(); }