+#define FLAG_CANAL 0x1
+#define FLAG_IS_TRIGGER 0x2
+#define FLAG_RESERVED0 0x4
+#define FLAG_RESERVED1 0x8
+
+#define FLAG_INPUT 0x10
+#define FLAG_OUTPUT 0x20
+#define FLAG_WALL 0x40
+
+#define FLAG_FLIP_FLOP 0x100
+#define FLAG_TRIGGERED 0x200
+#define FLAG_FLIP_ROTATING 0x400
+#define FLAG_TARGETED 0x800
+
+/*
+ 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=
+ | | | | | | |
+*/
+
+struct cell_description
+{
+ v2i start;
+ v2i end;
+
+ int is_special;
+ int is_linear;
+}
+cell_descriptions[] =
+{
+ // 0-3
+ {},
+ { .start = { 1, 0 }, .end = { -1, 0 } },
+ { .start = { 0, 1 }, .end = { 0, -1 } },
+ { .start = { 0, 1 }, .end = { 1, 0 } },
+ // 4-7
+ { .start = { -1, 0 }, .end = { 1, 0 } },
+ { .start = { -1, 0 }, .end = { 1, 0 }, .is_linear = 1 },
+ { .start = { 0, 1 }, .end = { -1, 0 } },
+ { .start = { 0, 1 }, .is_special = 1 },
+ // 8-11
+ { .start = { 0, -1 }, .end = { 0, 1 } },
+ { .start = { 1, 0 }, .end = { 0, -1 } },
+ { .start = { 0, 1 }, .end = { 0, -1 }, .is_linear = 1 },
+ { },
+ // 12-15
+ { .start = { -1, 0 }, .end = { 0, -1 } },
+ { .end = { 0, -1 }, .is_special = 1 },
+ { },
+ { }
+};
+
+enum cell_type
+{
+ k_cell_type_stub = 0,
+ k_cell_type_ramp_right = 3,
+ k_cell_type_ramp_left = 6,
+ k_cell_type_split = 7,
+ k_cell_type_merge = 13,
+ k_cell_type_con_r = 1,
+ k_cell_type_con_u = 2,
+ k_cell_type_con_l = 4,
+ k_cell_type_con_d = 8
+};
+
+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_1[] = {{1.0f,0.5f},{0.8f,0.5f},{0.3f,0.5f},{0.2f,0.5f}};
+v2f const curve_4[] = {{0.0f,0.5f},{0.3f,0.5f},{0.5f,0.5f},{0.8f,0.5f}};
+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.0f},{0.5f,0.3f},{0.5f,0.5f},{0.5f,0.8f}};
+
+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;
+
+v3f colour_sets[] =
+{ { 1.0f, 0.9f, 0.3f },
+ { 0.2f, 0.9f, 0.14f },
+ { 0.4f, 0.8f, 1.00f } };
+
+static void colour_code_v3( char cc, v3f target )
+{
+ if( cc >= 'a' && cc <= 'z' )
+ {
+ int id = cc - 'a';
+
+ if( id < vg_list_size( colour_sets ) )
+ {
+ v3_copy( colour_sets[ id ], target );
+ return;
+ }
+ }
+
+ v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
+}
+
+struct mesh
+{
+ GLuint vao, vbo;
+ u32 elements;
+};
+
+static void init_mesh( struct mesh *m, float const *tris, u32 length )
+{
+ m->elements = length/3;
+ glGenVertexArrays( 1, &m->vao );
+ glGenBuffers( 1, &m->vbo );
+
+ glBindVertexArray( m->vao );
+ glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
+ glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
+
+ glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ VG_CHECK_GL();
+}
+
+static void free_mesh( struct mesh *m )
+{
+ glDeleteVertexArrays( 1, &m->vao );
+ glDeleteBuffers( 1, &m->vbo );
+}
+
+static void draw_mesh( int const start, int const count )
+{
+ glDrawArrays( GL_TRIANGLES, start*3, count*3 );
+}
+
+static void use_mesh( struct mesh *m )
+{
+ glBindVertexArray( m->vao );
+}
+
+enum e_fish_state
+{
+ k_fish_state_soon_dead = -1,
+ k_fish_state_dead = 0,
+ k_fish_state_alive,
+ k_fish_state_bg,
+ k_fish_state_soon_alive
+};
+
+struct world
+{
+#pragma pack(push,1)
+ struct cell
+ {
+ u16 state;
+ u16 links[2];
+ u8 config;
+ u8 pad0;
+ }
+ *data;
+#pragma pack(pop)
+
+ int frame;
+
+ int initialzed;
+
+ int sim_frame;
+ float sim_start;
+ int simulating;
+ int sim_run, max_runs;
+
+ float sim_speed;
+ float frame_lerp;
+
+ struct cell_terminal
+ {
+ //char *conditions;
+ //char recv[12];
+
+ struct terminal_run
+ {
+ char conditions[8];
+ char recieved[8];
+
+ int condition_count, recv_count;
+ }
+ runs[8];
+
+ int run_count;
+
+ int id;
+ }
+ *io;
+
+ int w, h;
+
+ struct mesh tile, circle, numbers;
+ struct mesh_wire
+ {
+ GLuint vao, vbo, ebo;
+ u32 em;
+ }
+ wire;
+
+ GLuint background_data;
+ GLuint random_samples;
+
+ int selected, tile_x, tile_y;
+ v2f tile_pos;
+
+ struct fish
+ {
+ v2i pos;
+ v2i dir;
+ enum e_fish_state state;
+ char payload;
+ int flow_reversed;
+ float death_time;
+ v2f physics_v;
+ v2f physics_co;
+ }
+ fishes[16];
+
+ int num_fishes;
+
+ char map_name[64];
+ struct career_level *ptr_career_level;
+
+ u32 score;
+ u32 completed;
+ u32 time;
+} world = {};
+
+static void map_free(void)
+{
+ arrfree( world.data );
+ arrfree( world.io );
+
+ world.w = 0;
+ world.h = 0;
+ world.data = NULL;
+ world.io = NULL;
+ world.score = 0;
+ world.time = 0;
+ world.completed = 0;
+ world.max_runs = 0;
+ world.initialzed = 0;
+}
+
+static void io_reset(void)
+{
+ for( int i = 0; i < arrlen( world.io ); i ++ )
+ {
+ struct cell_terminal *term = &world.io[i];
+
+ for( int j = 0; j < term->run_count; j ++ )
+ term->runs[j].recv_count = 0;
+ }
+}
+
+static void map_reclassify( v2i start, v2i end, int update_texbuffer );
+static int map_load( const char *str, const char *name )
+{
+ //TODO: It may be worthwhile, at this point, to switch to binary encoding for save data
+
+ map_free();
+
+ char const *c = str;
+
+ // Scan for width
+ for(;; world.w ++)
+ {
+ if( c[world.w] == ';' )
+ break;
+ else if( !c[world.w] )
+ {
+ vg_error( "Unexpected EOF when parsing level\n" );
+ return 0;
+ }
+ }
+
+ struct cell *row = arraddnptr( world.data, world.w );
+ int cx = 0;
+ int reg_start = 0, reg_end = 0;
+
+ u32 *links_to_make = NULL;
+ int links_satisfied = 0;
+
+ char link_id_buffer[32];
+ int link_id_n = 0;
+
+ for(;;)
+ {
+ if( !*c )
+ break;
+
+ if( *c == '\r' ) { c ++; continue; } // fuck off windows
+
+ if( *c == ';' )
+ {
+ c ++;
+
+ if( *c == '\r' ) c ++;
+
+ // Parse attribs
+ if( *c != '\n' )
+ {
+ while( *c )
+ {
+ if( *c == '\r' ) { c ++; continue; }
+
+ if( reg_start < reg_end )
+ {
+ struct cell_terminal *terminal = &world.io[ reg_start ];
+ struct terminal_run *run = &terminal->runs[ terminal->run_count-1 ];
+
+ if( *c >= 'a' && *c <= 'z' )
+ {
+ run->conditions[ run->condition_count ++ ] = *c;
+ }
+ else
+ {
+ if( *c == ',' || *c == '\n' )
+ {
+ reg_start ++;
+
+ if( *c == '\n' )
+ break;
+ }
+ else if( *c == ':' )
+ {
+ terminal->runs[ terminal->run_count ].condition_count = 0;
+ terminal->run_count ++;
+ world.max_runs = vg_max( world.max_runs, terminal->run_count );
+ }
+ else
+ {
+ vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
+ goto IL_REG_ERROR;
+ }
+ }
+ }
+ else
+ {
+ if( links_satisfied < arrlen( links_to_make ) )
+ {
+ struct cell *target = &world.data[ links_to_make[ links_satisfied ] ];
+
+ if( (((u32)*c >= (u32)'0') && ((u32)*c <= (u32)'9')) || *c == '-' )
+ {
+ if( link_id_n >= vg_list_size( link_id_buffer )-1 )
+ {
+ vg_error( "Number was way too long to be parsed (row: %u)\n", world.h );
+ goto IL_REG_ERROR;
+ }
+
+ link_id_buffer[ link_id_n ++ ] = *c;
+ }
+ else if( *c == ',' || *c == '\n' )
+ {
+ link_id_buffer[ link_id_n ] = 0x00;
+ int value = atoi( link_id_buffer );
+
+ target->links[value >= 0? 1:0] = abs(value);
+ links_satisfied ++;
+ link_id_n = 0;
+
+ if( *c == '\n' )
+ break;
+ }
+ else
+ {
+ vg_error( "Invalid character '%c' (row: %u)\n", *c, world.h );
+ goto IL_REG_ERROR;
+ }
+ }
+ else
+ {
+ vg_error( "Too many values to assign (row: %u)\n", world.h );
+ goto IL_REG_ERROR;
+ }
+ }
+
+ c ++;
+ }
+ }
+
+ // Registry length-error checks
+ if( reg_start != reg_end )
+ {
+ vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
+ goto IL_REG_ERROR;
+ }
+
+ if( links_satisfied != arrlen( links_to_make ) )
+ {
+ vg_error( "Not enough link values assigned (row: %u, %u of %u)\n", world.h, links_satisfied, arrlen( links_to_make ) );
+ goto IL_REG_ERROR;
+ }
+
+ if( cx != world.w )
+ {
+ vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
+ goto IL_REG_ERROR;
+ }
+
+ row = arraddnptr( world.data, world.w );
+ cx = 0;
+ world.h ++;
+ reg_end = reg_start = arrlen( world.io );
+
+ arrsetlen( links_to_make, 0 );
+ links_satisfied = 0;
+ }
+ else
+ {
+ if( cx == world.w )
+ {
+ vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
+ goto IL_REG_ERROR;
+ }
+
+ // Tile initialization
+ // row[ cx ] .. etc
+ struct cell *cell = &row[ cx ];
+ cell->config = 0xF;
+
+ if( *c == '+' || *c == '-' )
+ {
+ struct cell_terminal *term = arraddnptr( world.io, 1 );
+ term->id = cx + world.h*world.w;
+ term->run_count = 1;
+ term->runs[0].condition_count = 0;
+
+ cell->state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
+ reg_end ++;
+ }
+ else if( *c == '#' ) cell->state = FLAG_WALL;
+ else if( ((u32)*c >= (u32)'A') && ((u32)*c <= (u32)'A'+0xf) )
+ {
+ // Canal flag bits (4bit/16 value):
+ // 0: Canal present
+ // 1: Is trigger
+ // 2: Reserved
+ // 3: Reserved
+
+ cell->state = ((u32)*c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
+
+ if( cell->state & FLAG_IS_TRIGGER )
+ arrpush( links_to_make, cx + world.h*world.w );
+
+ cell->links[0] = 0;
+ cell->links[1] = 0;
+ world.score ++;
+ }
+ else cell->state = 0x00;
+
+ cx ++;
+ }
+
+ c ++;
+ }
+
+ // 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 );
+ }
+
+ arrfree( links_to_make );
+
+ map_reclassify( NULL, NULL, 1 );
+
+ // Validate links
+ for( int i = 0; i < world.h*world.w; i ++ )
+ {
+ struct cell *src = &world.data[i];
+ if( src->state & FLAG_IS_TRIGGER )
+ {
+ int link_id = src->links[0]?0:1;
+ if( src->links[link_id] <= world.h*world.w )
+ {
+ struct cell *target = &world.data[ src->links[link_id] ];
+ if( (target->state & FLAG_CANAL) && (target->config == k_cell_type_split) )
+ {
+ if( target->links[ link_id ] )
+ {
+ vg_error( "Link target was already targeted\n" );
+ goto IL_REG_ERROR;
+ }
+ else
+ {
+ // Valid link
+ target->links[ link_id ] = i;
+ target->state |= FLAG_TARGETED;
+ }
+ }
+ else
+ {
+ vg_error( "Link target was invalid\n" );
+ goto IL_REG_ERROR;
+ }
+ }
+ else
+ {
+ vg_error( "Link target out of bounds\n" );
+ goto IL_REG_ERROR;
+ }
+ }
+ }
+
+ vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
+
+ io_reset();
+
+ strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
+ world.initialzed = 1;
+ return 1;
+
+IL_REG_ERROR:
+ arrfree( links_to_make );
+ map_free();
+ return 0;
+}
+
+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|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1) )
+ {
+ fputc( (cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)) + (u32)'A', 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 < term->run_count; j ++ )
+ {
+ struct terminal_run *run = &term->runs[j];
+
+ for( int k = 0; k < run->condition_count; k ++ )
+ fputc( run->conditions[k], stream );
+
+ if( j < term->run_count-1 )
+ fputc( ':', stream );
+ }
+ }
+ }
+ }
+
+ for( int x = 0; x < world.w; x ++ )
+ {
+ struct cell *cell = pcell( (v2i){ x,y } );
+ if( cell->state & FLAG_IS_TRIGGER )
+ {
+ if( terminal_write_count )
+ fputc( ',', stream );
+ terminal_write_count ++;
+
+ fprintf( stream, "%d", cell->links[0]? -cell->links[0]: cell->links[1] );
+ }
+ }
+
+ fputc( '\n', stream );
+ }
+}
+
+int main( int argc, char *argv[] )
+{
+ vg_init( argc, argv, "Marble Computing | SPACE: Test | LeftClick: Toggle tile | RightClick: Drag wire" );
+ return 0;
+}
+
+static int console_credits( int argc, char const *argv[] )
+{
+ vg_info( "Aknowledgements:\n" );
+ vg_info( " GLFW zlib/libpng glfw.org\n" );
+ vg_info( " miniaudio MIT0 miniaud.io\n" );
+ vg_info( " QOI MIT phoboslab.org\n" );
+ vg_info( " STB library MIT nothings.org\n" );
+ vg_info( " Weiholmir font justfredrik.itch.io\n" );
+ return 0;
+}
+
+static int console_save_map( int argc, char const *argv[] )
+{
+ if( !world.initialzed )
+ {
+ vg_error( "Tried to save uninitialized map!\n" );
+ return 0;
+ }
+
+ 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 )
+ {
+ vg_info( "Saving map to '%s'\n", map_path );
+ 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 )
+ {
+ vg_info( "Loading map: '%s'\n", map_path );
+ world.ptr_career_level = NULL;
+
+ if( !map_load( text_source, argv[0] ) )
+ {
+ free( text_source );
+ return 0;
+ }
+
+ free( text_source );
+
+ 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 <map_path>\n" );
+ return 0;
+ }
+}
+
+static void simulation_stop(void)
+{
+ world.simulating = 0;
+ world.num_fishes = 0;
+ world.sim_frame = 0;
+ world.sim_run = 0;
+
+ io_reset();
+
+ sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
+
+ vg_info( "Stopping simulation!\n" );
+}
+
+static int console_changelevel( int argc, char const *argv[] )
+{
+ if( argc >= 1 )
+ {
+ // Save current level
+ console_save_map( 0, NULL );
+ if( console_load_map( argc, argv ) )
+ {
+ simulation_stop();
+ return 1;
+ }
+ }
+ else
+ {
+ vg_error( "Missing argument <map_path>\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 = "map",
+ .function = console_changelevel
+ });
+
+ vg_function_push( (struct vg_cmd){
+ .name = "credits",
+ .function = console_credits
+ });
+
+ // Quad mesh
+ {
+ float quad_mesh[] =
+ {
+ 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.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) );
+ }
+
+ // Circle mesh
+ {
+ float circle_mesh[32*6*3];
+ int res = vg_list_size( circle_mesh ) / (6*3);
+
+ for( int i = 0; i < res; i ++ )
+ {
+ v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
+ v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
+
+ circle_mesh[ i*6+0 ] = 0.0f;
+ circle_mesh[ i*6+1 ] = 0.0f;
+
+ v2_copy( v0, circle_mesh + 32*6 + i*12 );
+ v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
+ v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
+
+ v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
+ v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
+ v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
+
+ v2_copy( v0, circle_mesh + i*6+4 );
+ v2_copy( v1, circle_mesh + i*6+2 );
+ v2_copy( v0, circle_mesh+i*6+4 );
+ v2_copy( v1, circle_mesh+i*6+2 );
+ }
+
+ init_mesh( &world.circle, circle_mesh, vg_list_size( circle_mesh ) );
+ }
+
+ // Numbers mesh
+ {
+ init_mesh( &world.numbers,
+ MESH_NUMBERS_BUFFER,
+ vg_list_size( MESH_NUMBERS_BUFFER )
+ );
+
+ for( int i = 0; i < 10; i ++ )
+ {
+ vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] );
+ }
+ }
+
+ // Create wire mesh
+ {
+ int const num_segments = 64;
+
+ struct mesh_wire *mw = &world.wire;
+
+ v2f wire_points[ num_segments * 2 ];
+ u16 wire_indices[ 6*(num_segments-1) ];
+
+ for( int i = 0; i < num_segments; i ++ )
+ {
+ float l = (float)i / (float)(num_segments-1);
+
+ v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
+ v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
+
+ if( i < num_segments-1 )
+ {
+ wire_indices[ i*6+0 ] = i*2 + 0;
+ wire_indices[ i*6+1 ] = i*2 + 1;
+ wire_indices[ i*6+2 ] = i*2 + 3;
+ wire_indices[ i*6+3 ] = i*2 + 0;
+ wire_indices[ i*6+4 ] = i*2 + 3;
+ wire_indices[ i*6+5 ] = i*2 + 2;
+ }
+ }
+
+ glGenVertexArrays( 1, &mw->vao );
+ glGenBuffers( 1, &mw->vbo );
+ glGenBuffers( 1, &mw->ebo );
+ glBindVertexArray( mw->vao );
+
+ glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
+
+ glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
+ glBindVertexArray( mw->vao );
+
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
+
+ // XY
+ glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ VG_CHECK_GL();
+
+ mw->em = vg_list_size( wire_indices );
+ }
+
+ // 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);
+
+ 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 );
+ }
+
+ resource_load_main();
+
+ // 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 );
+
+ glDeleteVertexArrays( 1, &world.wire.vao );
+ glDeleteBuffers( 1, &world.wire.vbo );
+ glDeleteBuffers( 1, &world.wire.ebo );
+
+ free_mesh( &world.tile );
+ free_mesh( &world.circle );
+ free_mesh( &world.numbers );
+
+ map_free();
+}
+
+static int world_check_pos_ok( v2i co )
+{
+ return (co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2)? 0: 1;
+}
+
+static int cell_interactive( v2i co )
+{
+ // Bounds check
+ if( !world_check_pos_ok( co ) )
+ return 0;
+
+ // Flags check
+ if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
+ return 0;
+
+ // List of 3x3 configurations that we do not allow
+ static u32 invalid_src[][9] =
+ {
+ { 0,1,0,
+ 1,1,1,
+ 0,1,0
+ },
+ { 0,0,0,
+ 0,1,1,
+ 0,1,1
+ },
+ { 0,0,0,
+ 1,1,0,
+ 1,1,0
+ },
+ { 0,1,1,
+ 0,1,1,
+ 0,0,0
+ },
+ { 1,1,0,
+ 1,1,0,
+ 0,0,0
+ },
+ { 0,1,0,
+ 0,1,1,
+ 0,1,0
+ },
+ { 0,1,0,
+ 1,1,0,
+ 0,1,0
+ }
+ };
+
+ // Statically compile invalid configurations into bitmasks
+ static u32 invalid[ vg_list_size(invalid_src) ];
+
+ for( int i = 0; i < vg_list_size(invalid_src); i ++ )
+ {
+ u32 comped = 0x00;
+
+ for( int j = 0; j < 3; j ++ )
+ for( int k = 0; k < 3; k ++ )
+ comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
+
+ invalid[i] = comped;
+ }
+
+ // Extract 5x5 grid surrounding tile
+ u32 blob = 0x1000;
+ for( int y = co[1]-2; y < co[1]+3; y ++ )
+ for( int x = co[0]-2; x < co[0]+3; x ++ )
+ {
+ struct cell *cell = pcell((v2i){x,y});
+
+ if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
+ blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
+ }
+
+ // Run filter over center 3x3 grid to check for invalid configurations
+ int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
+ for( int i = 0; i < vg_list_size(kernel); i ++ )
+ {
+ if( blob & (0x1 << (6+kernel[i])) )
+ {
+ u32 window = blob >> kernel[i];
+
+ for( int j = 0; j < vg_list_size(invalid); j ++ )
+ if((window & invalid[j]) == invalid[j])
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static void map_reclassify( v2i start, v2i end, int update_texbuffer )
+{
+ v2i full_start = { 1,1 };
+ v2i full_end = { world.w-1, world.h-1 };
+
+ if( !start || !end )
+ {
+ 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});