+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" })
+)
+
+SHADER_DEFINE( shader_ball,
+ // VERTEX
+ "layout (location=0) in vec2 a_co;"
+ "uniform vec2 uOffset;"
+ "uniform mat3 uPv;"
+ ""
+ "out vec2 aTexCoords;"
+ ""
+ "void main()"
+ "{"
+ // Create texture coords
+ "aTexCoords = a_co;"
+
+ // Vertex transform
+ "vec3 worldpos = vec3( a_co * 0.5 - 0.25 + uOffset, 1.0 );"
+ "gl_Position = vec4( uPv * worldpos, 1.0 );"
+ "}",
+
+ // FRAGMENT
+ "out vec4 FragColor;"
+ ""
+ "uniform sampler2D uTexMain;"
+ "uniform vec3 uColour;"
+ ""
+ "in vec2 aTexCoords;"
+ ""
+ "void main()"
+ "{"
+ "vec4 glyph = texture( uTexMain, aTexCoords );"
+ "FragColor = vec4( uColour + glyph.rgb * 0.2, glyph.a );"
+ "}"
+ ,
+ UNIFORMS({ "uTexMain", "uColour", "uOffset", "uPv" })
+)
+
+SHADER_DEFINE( shader_tile_main,
+ // VERTEX
+ "layout (location=0) in vec2 a_co;"
+ "uniform vec4 uOffset;" // Tile x/y, uv x/y
+ "uniform mat3 uPv;"
+ "uniform mat2 uSubTransform;"
+ ""
+ "out vec4 aTexCoords;"
+ ""
+ "vec2 hash22(vec2 p)"
+ "{"
+ "vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));"
+ "p3 += dot(p3, p3.yzx+33.33);"
+ "return fract((p3.xx+p3.yz)*p3.zy);"
+ "}"
+ ""
+ "void main()"
+ "{"
+ // Create texture coords
+ "vec2 random_offset = floor(hash22(uOffset.xy) * 4.0) * 0.25;"
+ "vec2 edge_safe_coords = a_co * 0.98 + 0.01;"
+ "aTexCoords = vec4((edge_safe_coords + uOffset.zw) * 0.25, edge_safe_coords * 0.25 + random_offset );"
+
+ // Vertex transform
+ "vec2 subtransform = uSubTransform * (a_co-0.5) + 0.5;"
+ "vec3 worldpos = vec3( subtransform + uOffset.xy, 1.0 );"
+ "gl_Position = vec4( uPv * worldpos, 1.0 );"
+ "}",
+
+ // FRAGMENT
+ "out vec4 FragColor;"
+ ""
+ "uniform sampler2D uTexGlyphs;"
+ "uniform sampler2D uTexWood;"
+ ""
+ "in vec4 aTexCoords;"
+ ""
+ "void main()"
+ "{"
+ "vec3 shadowing_colour = vec3( 0.93, 0.88536, 0.8184 );"
+ "vec4 glyph = texture( uTexGlyphs, aTexCoords.xy );"
+ "vec4 wood = texture( uTexWood, aTexCoords.zw );"
+ "vec4 wood_secondary = texture( uTexWood, aTexCoords.zw + 0.25 );"
+ "vec3 wood_comp = mix( wood_secondary.rgb * shadowing_colour, wood.rgb, clamp( glyph.b * 2.0 - 1.0, 0.0, 1.0 ) );"
+
+ "vec3 shadows = mix( vec3( 0.85, 0.7344, 0.561 ), vec3(1.0,1.0,1.0), glyph.r );"
+
+ "FragColor = vec4( wood_comp * shadows, glyph.b );"
+ "}"
+ ,
+ UNIFORMS({ "uPv", "uOffset", "uTexGlyphs", "uTexWood", "uSubTransform" })
+)
+
+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"
+};
+
+GLuint tex_tile_data;
+GLuint tex_tile_detail;
+GLuint tex_wood;
+GLuint tex_ball;
+
+sfx_system_t audio_system_sfx =
+{
+ .vol = 1.f,
+ .spd = 1.f,
+ .ch = 1,
+ .cur = 0,
+ .vol_src = &g_vol_sfx,
+ .flags = 0x00,
+ .fadeout = FADEOUT_LENGTH,
+ .name = "sfx"
+};
+
+sfx_set_t audio_tile_mod =
+{
+ .sources = "\
+sound/mod_01.ogg\0\
+sound/mod_02.ogg\0\
+sound/mod_03.ogg\0\
+sound/mod_04.ogg\0\
+sound/mod_05.ogg\0\
+sound/mod_06.ogg\0",
+ .flags = 0
+};
+
+m3x3f m_projection;
+m3x3f m_view;
+m3x3f m_mdl;
+
+#define FLAG_INPUT 0x1
+#define FLAG_OUTPUT 0x2
+#define FLAG_CANAL 0x4
+#define FLAG_WALL 0x8
+#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_split = 7,
+ k_cell_type_merge = 13
+};
+
+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;
+
+ 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] ];
+}
+