+
+void vg_update(void)
+{
+ float ratio = (float)vg_window_y / (float)vg_window_x;
+ float const size = 9.5f;
+
+ v3f origin;
+ origin[0] = -0.5f * world.w;
+ origin[1] = -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();
+
+ v2f tile_pos;
+ v2_copy( vg_mouse_ws, tile_pos );
+
+ int tile_x = floorf( tile_pos[0] );
+ int tile_y = floorf( tile_pos[1] );
+
+ if( cell_interactive( (v2i){ tile_x, tile_y } ))
+ {
+ world.selected = tile_y * world.w + tile_x;
+
+ if( vg_get_button_down("primary") )
+ {
+ world.data[ world.selected ].state ^= FLAG_CANAL;
+ }
+ }
+ else
+ world.selected = -1;
+
+ // 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 ++ )
+ {
+ v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
+
+ u8 config = 0x00;
+
+ if( world.data[y*world.w+x].state & FLAG_CANAL )
+ {
+ for( int i = 0; i < vg_list_size( dirs ); i ++ )
+ {
+ struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
+ if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
+ config |= 0x1 << i;
+ }
+ } else config = 0xF;
+
+ world.data[ y*world.w+x ].config = config;
+ world.data[ y*world.w+x ].state &= ~(FLAG_DROP|FLAG_SPLIT);
+ }
+ }
+
+ 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 = world.data[y*world.w+x].config;
+
+ if( config == 0x7 )
+ {
+ world.data[y*world.w+x ].state |= (FLAG_SPLIT | FLAG_DROP);
+ }
+ else if( config == 0xD )
+ {
+ world.data[y*world.w+x-1].state |= FLAG_DROP;
+ world.data[y*world.w+x+1].state |= FLAG_DROP;
+ }
+ }
+
+ /*
+ canals
+ x
+ xxxxx
+ x x
+ x xxx
+ x x x
+ x xxx
+ x x
+ xxxxx
+ x
+ x
+
+ configs (Downlevels marked)
+ x
+ xDxDx
+ x x
+ x DxD
+ x x x
+ x xDx
+ x x
+ xxxDx
+ x
+ x
+
+ Path tracing with min function
+ 0
+ 11011
+ 1 .
+ 1 D.D
+ 1 . .
+ 1 .D.
+ 1 .
+ 1112.
+ 2
+ 2
+
+ Path tracing with min function
+ 0
+ 11011
+ 1 1
+ 1 212
+ 1 2 2
+ 1 232
+ 1 3
+ 11143
+ 4
+ 4
+
+ L to R fill
+ 0000000
+ 1101111
+ 1111111
+ 1112122
+ 1112222
+ 1112322
+ 1111333
+ 1114333
+ 4444444
+ 4444444
+ */
+
+ // Simulate world
+ static int update_tick = 0;
+ update_tick ++;
+
+ if( update_tick > 5 )
+ {
+ update_tick = 0;
+
+ u32 buffer_id = world.frame & 0x1;
+ u32 buffer_next = buffer_id ^ 0x1;
+
+ for( int y = 1; y < world.h-1; y ++ )
+ {
+ for( int x = 1; x < world.w-1; x ++ )
+ {
+ struct cell *cell = &world.data[y*world.w+x];
+
+ if( cell->state & (FLAG_OUTPUT|FLAG_DROP) )
+ cell->water[ buffer_next ] = 16;
+ else
+ {
+ int has_source = 0;
+ cell->water[ buffer_next ] = 0;
+
+ if( cell->state & FLAG_CANAL )
+ {
+ v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
+
+ for( int i = 0; i < vg_list_size( dirs ); i ++ )
+ {
+ struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
+
+ if( neighbour->water[ buffer_id ] > cell->water[ buffer_next ]+1 )
+ {
+ has_source = 1;
+ cell->water[ buffer_next ] = neighbour->water[ buffer_id ]-1;
+ }
+ }
+ }
+
+ if( !has_source && cell->water[ buffer_id ] )
+ cell->water[ buffer_next ] = cell->water[ buffer_id ]-1;
+ }
+ }
+ }
+
+ world.frame ++;
+ }
+}
+
+void vg_render(void)
+{
+ glViewport( 0,0, vg_window_x, vg_window_y );
+
+ glDisable( GL_DEPTH_TEST );
+ glClearColor( 0.01f, 0.01f, 0.01f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+
+ glBindVertexArray( world.tile_vao );
+ SHADER_USE( shader_tile_colour );
+ glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
+
+ for( int y = 0; y < world.h; y ++ )
+ {
+ for( int x = 0; x < world.w; x ++ )
+ {
+ glUniform2f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y );
+
+ v4f colour;
+
+ struct cell *cell = &world.data[y*world.w+x];
+
+ 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.2f, 0.3f, 0.7f, 1.0f }, colour ); }
+ else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.2f, 0.7f, 0.3f, 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/8.0f), 1.0f }, colour );
+
+ if( world.selected == y*world.w + x )
+ v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour );
+
+ if( cell->state & (FLAG_DROP|FLAG_SPLIT) )
+ v4_copy( (v4f){ 0.75f, 0.75f, 0.02f, 1.0f }, colour );
+
+ glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour );
+
+ glDrawArrays( GL_TRIANGLES, 0, 6 );
+ }
+ }
+}
+
+void vg_ui(void){}