flowing water between cells
[fishladder.git] / fishladder.c
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2
3 //#define VG_STEAM
4 #include "vg/vg.h"
5
6 SHADER_DEFINE( shader_tile_colour,
7
8 // VERTEX
9 "layout (location=0) in vec2 a_co;"
10 "uniform mat3 uPv;"
11 "uniform vec2 uOffset;"
12 ""
13 "void main()"
14 "{"
15 "gl_Position = vec4( uPv * vec3( a_co + uOffset, 1.0 ), 1.0 );"
16 "}",
17
18 // FRAGMENT
19 "out vec4 FragColor;"
20 "uniform vec4 uColour;"
21 ""
22 "void main()"
23 "{"
24 "FragColor = uColour;"
25 "}"
26 ,
27 UNIFORMS({ "uPv", "uOffset", "uColour" })
28 )
29
30 m3x3f m_projection;
31 m3x3f m_view;
32 m3x3f m_mdl;
33
34 #define FLAG_INPUT 0x1
35 #define FLAG_OUTPUT 0x2
36 #define FLAG_CANAL 0x4
37 #define FLAG_WALL 0x8
38
39 struct world
40 {
41 struct cell
42 {
43 u32 state;
44 u8 water;
45 }
46 *data;
47
48 struct cell_terminal
49 {
50 char *conditions;
51 int id;
52 }
53 *io;
54
55 u32 w, h;
56
57 GLuint tile_vao;
58 GLuint tile_vbo;
59
60 int selected;
61 } world = {};
62
63 static void map_free(void)
64 {
65 for( int i = 0; i < arrlen( world.io ); i ++ )
66 arrfree( world.io[ i ].conditions );
67
68 arrfree( world.data );
69 arrfree( world.io );
70
71 world.w = 0;
72 world.h = 0;
73 world.data = NULL;
74 world.io = NULL;
75 }
76
77 static int map_load( const char *str )
78 {
79 map_free();
80
81 char const *c = str;
82
83 // Scan for width
84 for(;; world.w ++)
85 {
86 if( str[world.w] == ';' )
87 break;
88 else if( !str[world.w] )
89 {
90 vg_error( "Unexpected EOF when parsing level\n" );
91 return 0;
92 }
93 }
94
95 struct cell *row = arraddnptr( world.data, world.w );
96 int cx = 0;
97 int reg_start = 0, reg_end = 0;
98
99 for(;;)
100 {
101 if( !*c )
102 break;
103
104 if( *c == ';' )
105 {
106 c ++;
107
108 // Parse attribs
109 if( *c != '\n' )
110 {
111 while( *c )
112 {
113 if( reg_start < reg_end )
114 {
115 if( *c >= 'a' && *c <= 'z' )
116 {
117 arrpush( world.io[ reg_start ].conditions, *c );
118 }
119 else
120 {
121 if( *c == ',' || *c == '\n' )
122 {
123 reg_start ++;
124
125 if( *c == '\n' )
126 break;
127 }
128 else
129 {
130 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
131 return 0;
132 }
133 }
134 }
135 else
136 {
137 vg_error( "Too many values to assign (row: %u)\n", world.h );
138 return 0;
139 }
140
141 c ++;
142 }
143 }
144
145 if( reg_start != reg_end )
146 {
147 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
148 return 0;
149 }
150
151 if( cx != world.w )
152 {
153 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
154 return 0;
155 }
156
157 row = arraddnptr( world.data, world.w );
158 cx = 0;
159 world.h ++;
160 reg_end = reg_start = arrlen( world.io );
161 }
162 else
163 {
164 if( cx == world.w )
165 {
166 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
167 return 0;
168 }
169
170 // Tile initialization
171 // row[ cx ] .. etc
172 row[ cx ].water = 0;
173
174 if( *c == '+' || *c == '-' )
175 {
176 struct cell_terminal term = { .id = cx + world.h*world.w };
177 arrpush( world.io, term );
178 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
179 reg_end ++;
180 }
181 else if( *c == '#' )
182 {
183 row[ cx ++ ].state = FLAG_WALL;
184 }
185 else
186 {
187 row[ cx ++ ].state = 0x00;
188 }
189 }
190
191 c ++;
192 }
193
194 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
195 return 1;
196 }
197
198 int main( int argc, char *argv[] )
199 {
200 vg_init( argc, argv, "FishLadder" );
201 }
202
203 void vg_register(void)
204 {
205 SHADER_INIT( shader_tile_colour );
206 }
207
208 void vg_start(void)
209 {
210 glGenVertexArrays( 1, &world.tile_vao );
211 glGenBuffers( 1, &world.tile_vbo );
212
213 float quad_mesh[] =
214 {
215 0.05f, 0.05f, 0.05f, 0.95f, 0.95f, 0.95f,
216 0.05f, 0.05f, 0.95f, 0.95f, 0.95f, 0.05f
217 };
218
219 glBindVertexArray( world.tile_vao );
220 glBindBuffer( GL_ARRAY_BUFFER, world.tile_vbo );
221 glBufferData
222 (
223 GL_ARRAY_BUFFER,
224 sizeof( quad_mesh ),
225 quad_mesh,
226 GL_STATIC_DRAW
227 );
228
229 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
230 glEnableVertexAttribArray( 0 );
231
232 VG_CHECK_GL();
233
234 map_load
235 (
236 "#############;\n"
237 "###-#####-###;aaa,aa\n"
238 "## ##;\n"
239 "## ##;\n"
240 "## ##;\n"
241 "## ##;\n"
242 "## ##;\n"
243 "## ##;\n"
244 "###+#####+###;aa,aaa\n"
245 "#############;\n"
246 );
247 }
248
249 void vg_free(void)
250 {
251 glDeleteVertexArrays( 1, &world.tile_vao );
252 glDeleteBuffers( 1, &world.tile_vbo );
253
254 map_free();
255 }
256
257 void vg_update(void)
258 {
259 float ratio = (float)vg_window_y / (float)vg_window_x;
260 float const size = 9.5f;
261
262 v3f origin;
263 origin[0] = -0.5f * world.w;
264 origin[1] = -0.5f * world.h;
265 origin[2] = 0.0f;
266
267 m3x3_projection( m_projection, -size, size, size*ratio, -size*ratio );
268 m3x3_identity( m_view );
269 m3x3_translate( m_view, origin );
270 m3x3_mul( m_projection, m_view, vg_pv );
271 vg_projection_update();
272
273 v2f tile_pos;
274 v2_copy( vg_mouse_ws, tile_pos );
275
276 int tile_x = floorf( tile_pos[0] );
277 int tile_y = floorf( tile_pos[1] );
278
279 if( tile_x >= 2 && tile_x < world.w-2 && tile_y >= 2 && tile_y <= world.h-2 )
280 {
281 world.selected = tile_y * world.w + tile_x;
282
283 struct cell *cell = &world.data[tile_y*world.w+tile_x];
284 if( cell->state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
285 {
286 world.selected = -1;
287 }
288 else
289 {
290 if( vg_get_button_down("primary") )
291 {
292 cell->state ^= FLAG_CANAL;
293 }
294 }
295 }
296 else
297 world.selected = -1;
298
299 for( int y = 1; y < world.h-1; y ++ )
300 {
301 for( int x = 1; x < world.w-1; x ++ )
302 {
303 struct cell *cell = &world.data[y*world.w+x];
304
305 if( !(cell->state & FLAG_CANAL) )
306 cell->water = 0;
307
308 if( cell->state & FLAG_INPUT )
309 cell->water = 8;
310
311 if( cell->water )
312 {
313 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
314
315 for( int i = 0; i < vg_list_size( dirs ); i ++ )
316 {
317 struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
318
319 if( neighbour->state & FLAG_CANAL )
320 {
321 neighbour->water = vg_max( neighbour->water, cell->water-1 );
322 }
323 }
324
325 cell->water --;
326 }
327 }
328 }
329 }
330
331 void vg_render(void)
332 {
333 glViewport( 0,0, vg_window_x, vg_window_y );
334
335 glDisable( GL_DEPTH_TEST );
336 glClearColor( 0.01f, 0.01f, 0.01f, 1.0f );
337 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
338
339 glBindVertexArray( world.tile_vao );
340 SHADER_USE( shader_tile_colour );
341 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
342
343 for( int y = 0; y < world.h; y ++ )
344 {
345 for( int x = 0; x < world.w; x ++ )
346 {
347 glUniform2f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y );
348
349 v4f colour;
350
351 struct cell *cell = &world.data[y*world.w+x];
352
353 if( cell->state & FLAG_WALL ) { v4_copy( (v4f){ 0.2f, 0.2f, 0.2f, 1.0f }, colour ); }
354 else if( cell->state & FLAG_CANAL ) { v4_copy( (v4f){ 0.6f, 0.6f, 0.6f, 1.0f }, colour ); }
355 else if( cell->state & FLAG_INPUT ) { v4_copy( (v4f){ 0.2f, 0.3f, 0.7f, 1.0f }, colour ); }
356 else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.2f, 0.7f, 0.3f, 1.0f }, colour ); }
357 else v4_copy( (v4f){ 0.9f, 0.9f, 0.9f, 1.0f }, colour );
358
359 if( cell->water )
360 v4_copy( (v4f){ 0.2f, 0.3f, 0.7f * (float)(cell->water) * (1.0f/8.0f), 1.0f }, colour );
361
362 if( world.selected == y*world.w + x )
363 v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour );
364
365 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour );
366
367 glDrawArrays( GL_TRIANGLES, 0, 6 );
368 }
369 }
370 }
371
372 void vg_ui(void){}