+#define FLAG_INPUT 0x1
+#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
+
+v3f colour_sets[] =
+{ { 0.9f, 0.2f, 0.01f },
+ { 0.2f, 0.9f, 0.14f },
+ { 0.1f, 0.3f, 0.85f } };
+
+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 *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 );
+}
+
+struct world
+{
+ struct cell
+ {
+ u32 state;
+ u8 config;
+ }
+ *data;
+
+ u32 frame;
+
+ u32 sim_frame;
+ float sim_start;
+ int simulating;
+
+ struct cell_terminal
+ {
+ // TODO: Split into input/output structures
+ char *conditions;
+ char recv[12];
+ int recv_count;
+ int id;
+ }
+ *io;
+
+ u32 w, h;
+
+ struct mesh tile, circle, splitter_l, splitter_r;
+
+ int selected;
+
+ struct fish
+ {
+ v2i pos;
+ v2i dir;
+ int alive;
+ char payload;
+ }
+ fishes[16];
+
+ int num_fishes;
+} world = {};
+
+static void map_free(void)
+{
+ for( int i = 0; i < arrlen( world.io ); i ++ )
+ arrfree( world.io[ i ].conditions );
+
+ arrfree( world.data );
+ arrfree( world.io );
+
+ world.w = 0;
+ world.h = 0;
+ world.data = NULL;
+ world.io = NULL;
+}
+
+static int map_load( const char *str )
+{
+ map_free();
+
+ char const *c = str;
+
+ // Scan for width
+ for(;; world.w ++)
+ {
+ if( str[world.w] == ';' )
+ break;
+ else if( !str[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;
+
+ for(;;)
+ {
+ if( !*c )
+ break;
+
+ if( *c == ';' )
+ {
+ c ++;
+
+ // Parse attribs
+ if( *c != '\n' )
+ {
+ while( *c )
+ {
+ if( reg_start < reg_end )
+ {
+ if( *c >= 'a' && *c <= 'z' )
+ {
+ arrpush( world.io[ reg_start ].conditions, *c );
+ }
+ else
+ {
+ if( *c == ',' || *c == '\n' )
+ {
+ reg_start ++;
+
+ if( *c == '\n' )
+ break;
+ }
+ else
+ {
+ vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
+ return 0;
+ }
+ }
+ }
+ else
+ {
+ vg_error( "Too many values to assign (row: %u)\n", world.h );
+ return 0;
+ }
+
+ c ++;
+ }
+ }
+
+ if( reg_start != reg_end )
+ {
+ vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
+ return 0;
+ }
+
+ if( cx != world.w )
+ {
+ vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
+ return 0;
+ }
+
+ row = arraddnptr( world.data, world.w );
+ cx = 0;
+ world.h ++;
+ reg_end = reg_start = arrlen( world.io );
+ }
+ 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 );
+ return 0;
+ }
+
+ // Tile initialization
+ // row[ cx ] .. etc
+
+ 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;
+ reg_end ++;
+ }
+ else if( *c == '#' )
+ {
+ row[ cx ++ ].state = FLAG_WALL;
+ }
+ else
+ {
+ row[ cx ++ ].state = 0x00;
+ }
+ }
+
+ c ++;
+ }
+
+ vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
+ return 1;
+}
+
+static struct cell *pcell( v2i pos )
+{
+ return &world.data[ pos[1]*world.w + pos[0] ];
+}
+
+int main( int argc, char *argv[] )
+{
+ vg_init( argc, argv, "FishLadder" );
+}
+
+void vg_register(void)
+{
+ SHADER_INIT( shader_tile_colour );
+}
+
+void vg_start(void)
+{
+ // 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.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
+ };
+
+ 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 ) );
+ }
+
+ // splitters (temp)
+ {
+ float splitter_l[] =
+ {
+ #include "models/splitter_l.obj.h"
+ };
+ float splitter_r[] =
+ {
+ #include "models/splitter_r.obj.h"
+ };
+
+ init_mesh( &world.splitter_l, splitter_l, vg_list_size( splitter_l ) );
+ init_mesh( &world.splitter_r, splitter_r, vg_list_size( splitter_r ) );
+ }
+
+ map_load
+ (
+ "#############;\n"
+ "###-#####-###;aaa,aa\n"
+ "## ##;\n"
+ "## ##;\n"
+ "## ##;\n"
+ "## ##;\n"
+ "## ##;\n"
+ "## ##;\n"
+ "###+#####+###;aa,aaa\n"
+ "#############;\n"
+ );
+}