split/merge working with water
[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 #define FLAG_DROP_L 0x10
39 #define FLAG_SPLIT 0x20
40 #define FLAG_MERGER 0x40
41 #define FLAG_DROP_R 0x80
42
43 struct world
44 {
45 struct cell
46 {
47 u32 state;
48 u8 water[2];
49 u8 config;
50 u8 water_dir;
51 }
52 *data;
53
54 u32 frame;
55
56 struct cell_terminal
57 {
58 char *conditions;
59 int id;
60 }
61 *io;
62
63 u32 w, h;
64
65 GLuint tile_vao;
66 GLuint tile_vbo;
67
68 int selected;
69 } world = {};
70
71 static void map_free(void)
72 {
73 for( int i = 0; i < arrlen( world.io ); i ++ )
74 arrfree( world.io[ i ].conditions );
75
76 arrfree( world.data );
77 arrfree( world.io );
78
79 world.w = 0;
80 world.h = 0;
81 world.data = NULL;
82 world.io = NULL;
83 }
84
85 static int map_load( const char *str )
86 {
87 map_free();
88
89 char const *c = str;
90
91 // Scan for width
92 for(;; world.w ++)
93 {
94 if( str[world.w] == ';' )
95 break;
96 else if( !str[world.w] )
97 {
98 vg_error( "Unexpected EOF when parsing level\n" );
99 return 0;
100 }
101 }
102
103 struct cell *row = arraddnptr( world.data, world.w );
104 int cx = 0;
105 int reg_start = 0, reg_end = 0;
106
107 for(;;)
108 {
109 if( !*c )
110 break;
111
112 if( *c == ';' )
113 {
114 c ++;
115
116 // Parse attribs
117 if( *c != '\n' )
118 {
119 while( *c )
120 {
121 if( reg_start < reg_end )
122 {
123 if( *c >= 'a' && *c <= 'z' )
124 {
125 arrpush( world.io[ reg_start ].conditions, *c );
126 }
127 else
128 {
129 if( *c == ',' || *c == '\n' )
130 {
131 reg_start ++;
132
133 if( *c == '\n' )
134 break;
135 }
136 else
137 {
138 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
139 return 0;
140 }
141 }
142 }
143 else
144 {
145 vg_error( "Too many values to assign (row: %u)\n", world.h );
146 return 0;
147 }
148
149 c ++;
150 }
151 }
152
153 if( reg_start != reg_end )
154 {
155 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
156 return 0;
157 }
158
159 if( cx != world.w )
160 {
161 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
162 return 0;
163 }
164
165 row = arraddnptr( world.data, world.w );
166 cx = 0;
167 world.h ++;
168 reg_end = reg_start = arrlen( world.io );
169 }
170 else
171 {
172 if( cx == world.w )
173 {
174 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
175 return 0;
176 }
177
178 // Tile initialization
179 // row[ cx ] .. etc
180 row[ cx ].water[0] = 0;
181 row[ cx ].water[1] = 0;
182
183 if( *c == '+' || *c == '-' )
184 {
185 struct cell_terminal term = { .id = cx + world.h*world.w };
186 arrpush( world.io, term );
187 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
188 reg_end ++;
189 }
190 else if( *c == '#' )
191 {
192 row[ cx ++ ].state = FLAG_WALL;
193 }
194 else
195 {
196 row[ cx ++ ].state = 0x00;
197 }
198 }
199
200 c ++;
201 }
202
203 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
204 return 1;
205 }
206
207 int main( int argc, char *argv[] )
208 {
209 vg_init( argc, argv, "FishLadder" );
210 }
211
212 void vg_register(void)
213 {
214 SHADER_INIT( shader_tile_colour );
215 }
216
217 void vg_start(void)
218 {
219 glGenVertexArrays( 1, &world.tile_vao );
220 glGenBuffers( 1, &world.tile_vbo );
221
222 float quad_mesh[] =
223 {
224 0.05f, 0.05f, 0.05f, 0.95f, 0.95f, 0.95f,
225 0.05f, 0.05f, 0.95f, 0.95f, 0.95f, 0.05f,
226
227 0.48f, 0.48f, 0.5f, 0.52f, 0.52f, 0.52f, // Static dot
228 0.375f, 0.25f, 0.5f, 0.75f, 0.625f, 0.25f, // Downwards pointing arrow
229 0.25f, 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, // Left
230 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, 0.75f, // up
231 0.75f, 0.375f, 0.25f, 0.5f, 0.75f, 0.625f
232
233 };
234
235 glBindVertexArray( world.tile_vao );
236 glBindBuffer( GL_ARRAY_BUFFER, world.tile_vbo );
237 glBufferData
238 (
239 GL_ARRAY_BUFFER,
240 sizeof( quad_mesh ),
241 quad_mesh,
242 GL_STATIC_DRAW
243 );
244
245 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
246 glEnableVertexAttribArray( 0 );
247
248 VG_CHECK_GL();
249
250 map_load
251 (
252 "#############;\n"
253 "###-#####-###;aaa,aa\n"
254 "## ##;\n"
255 "## ##;\n"
256 "## ##;\n"
257 "## ##;\n"
258 "## ##;\n"
259 "## ##;\n"
260 "###+#####+###;aa,aaa\n"
261 "#############;\n"
262 );
263 }
264
265 void vg_free(void)
266 {
267 glDeleteVertexArrays( 1, &world.tile_vao );
268 glDeleteBuffers( 1, &world.tile_vbo );
269
270 map_free();
271 }
272
273 static int cell_interactive( v2i co )
274 {
275 // Bounds check
276 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
277 return 0;
278
279 // Flags check
280 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
281 return 0;
282
283 // List of 3x3 configurations that we do not allow
284 static u32 invalid_src[][9] =
285 {
286 { 0,1,0,
287 1,1,1,
288 0,1,0
289 },
290 { 0,0,0,
291 0,1,1,
292 0,1,1
293 },
294 { 0,0,0,
295 1,1,0,
296 1,1,0
297 },
298 { 0,1,1,
299 0,1,1,
300 0,0,0
301 },
302 { 1,1,0,
303 1,1,0,
304 0,0,0
305 },
306 { 0,1,0,
307 0,1,1,
308 0,1,0
309 },
310 { 0,1,0,
311 1,1,0,
312 0,1,0
313 }
314 };
315
316 // Statically compile invalid configurations into bitmasks
317 static u32 invalid[ vg_list_size(invalid_src) ];
318
319 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
320 {
321 u32 comped = 0x00;
322
323 for( int j = 0; j < 3; j ++ )
324 for( int k = 0; k < 3; k ++ )
325 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
326
327 invalid[i] = comped;
328 }
329
330 // Extract 5x5 grid surrounding tile
331 u32 blob = 0x1000;
332 for( int y = co[1]-2; y < co[1]+3; y ++ )
333 for( int x = co[0]-2; x < co[0]+3; x ++ )
334 {
335 struct cell *cell = &world.data[ world.w*y + x ];
336
337 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
338 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
339 }
340
341 // Run filter over center 3x3 grid to check for invalid configurations
342 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
343 for( int i = 0; i < vg_list_size(kernel); i ++ )
344 {
345 if( blob & (0x1 << (6+kernel[i])) )
346 {
347 u32 window = blob >> kernel[i];
348
349 for( int j = 0; j < vg_list_size(invalid); j ++ )
350 if((window & invalid[j]) == invalid[j])
351 return 0;
352 }
353 }
354
355 return 1;
356 }
357
358 void vg_update(void)
359 {
360 float ratio = (float)vg_window_y / (float)vg_window_x;
361 float const size = 9.5f;
362
363 v3f origin;
364 origin[0] = -0.5f * world.w;
365 origin[1] = -0.5f * world.h;
366 origin[2] = 0.0f;
367
368 m3x3_projection( m_projection, -size, size, size*ratio, -size*ratio );
369 m3x3_identity( m_view );
370 m3x3_translate( m_view, origin );
371 m3x3_mul( m_projection, m_view, vg_pv );
372 vg_projection_update();
373
374 v2f tile_pos;
375 v2_copy( vg_mouse_ws, tile_pos );
376
377 int tile_x = floorf( tile_pos[0] );
378 int tile_y = floorf( tile_pos[1] );
379
380 if( cell_interactive( (v2i){ tile_x, tile_y } ))
381 {
382 world.selected = tile_y * world.w + tile_x;
383
384 if( vg_get_button_down("primary") )
385 {
386 world.data[ world.selected ].state ^= FLAG_CANAL;
387 }
388 }
389 else
390 world.selected = -1;
391
392 // Reclassify world. TODO: Move into own function
393 for( int y = 2; y < world.h-2; y ++ )
394 {
395 for( int x = 2; x < world.w-2; x ++ )
396 {
397 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
398
399 u8 config = 0x00;
400
401 if( world.data[y*world.w+x].state & FLAG_CANAL )
402 {
403 for( int i = 0; i < vg_list_size( dirs ); i ++ )
404 {
405 struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
406 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
407 config |= 0x1 << i;
408 }
409 } else config = 0xF;
410
411 world.data[ y*world.w+x ].config = config;
412 world.data[ y*world.w+x ].state &= ~(FLAG_DROP_L|FLAG_DROP_R|FLAG_SPLIT|FLAG_MERGER);
413 }
414 }
415
416 for( int y = 2; y < world.h-2; y ++ )
417 for( int x = 2; x < world.w-2; x ++ )
418 {
419 // R,D,L,- 1110 (splitter, 1 drop created)
420
421 // R,-,L,U - 1011 (merger, 2 drop created)
422
423 u8 config = world.data[y*world.w+x].config;
424
425 if( config == 0x7 ) // splitter
426 {
427 world.data[y*world.w+x ].state |= (FLAG_SPLIT | FLAG_DROP_L | FLAG_DROP_R);
428 }
429 else if( config == 0xD )
430 {
431 world.data[y*world.w+x-1].state |= FLAG_DROP_R;
432 world.data[y*world.w+x+1].state |= FLAG_DROP_L;
433 world.data[y*world.w+x].state |= FLAG_MERGER;
434 }
435 }
436
437 /*
438 canals
439 x
440 xxxxx
441 x x
442 x xxx
443 x x x
444 x xxx
445 x x
446 xxxxx
447 x
448 x
449
450 configs (Downlevels marked)
451 x
452 xDxDx
453 x x
454 x DxD
455 x x x
456 x xDx
457 x x
458 xxxDx
459 x
460 x
461
462 Path tracing with min function
463 0
464 11011
465 1 .
466 1 D.D
467 1 . .
468 1 .D.
469 1 .
470 1112.
471 2
472 2
473
474 Path tracing with min function
475 0
476 11011
477 1 1
478 1 212
479 1 2 2
480 1 232
481 1 3
482 11143
483 4
484 4
485
486 L to R fill
487 0000000
488 1101111
489 1111111
490 1112122
491 1112222
492 1112322
493 1111333
494 1114333
495 4444444
496 4444444
497 */
498
499 // Simulate world
500 static int update_tick = 0;
501 update_tick ++;
502
503 if( update_tick > 5 )
504 {
505 update_tick = 0;
506
507 u32 buffer_id = world.frame & 0x1;
508 u32 buffer_next = buffer_id ^ 0x1;
509
510 for( int y = 1; y < world.h-1; y ++ )
511 {
512 for( int x = 1; x < world.w-1; x ++ )
513 {
514 struct cell *cell = &world.data[y*world.w+x];
515
516 if( cell->state & FLAG_OUTPUT )
517 cell->water[ buffer_next ] = 16;
518 else
519 {
520 int has_source = 0;
521 cell->water[ buffer_next ] = 0;
522
523 if( cell->state & FLAG_CANAL )
524 {
525 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
526
527 for( int i = 0; i < vg_list_size( dirs ); i ++ )
528 {
529 struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
530
531 // Non canals will be ignored
532 if( !(neighbour->state & (FLAG_CANAL|FLAG_OUTPUT)) )
533 continue;
534
535 // Only vertical pulls allowed on neighbour splitters
536 if( (neighbour->state & FLAG_SPLIT) && i != 2 )
537 continue;
538
539 // Only vertical pulls allowed for mergers
540 if( (cell->state & FLAG_MERGER) && i != 2 )
541 continue;
542
543 // Test for renewall cases if we have drop L/R check if i matches direction.
544 if( (((cell->state & FLAG_DROP_L)&&i==1) || ((cell->state & FLAG_DROP_R)&&i==0)) && neighbour->water[ buffer_id ] )
545 {
546 cell->water[ buffer_next ] = 16;
547 has_source = 1;
548 }
549 else
550 if( neighbour->water[ buffer_id ] > cell->water[ buffer_next ]+1 )
551 {
552 has_source = 1;
553 cell->water[ buffer_next ] = neighbour->water[ buffer_id ]-1;
554 }
555 }
556 }
557
558 if( !has_source && cell->water[ buffer_id ] )
559 cell->water[ buffer_next ] = cell->water[ buffer_id ]-1;
560 }
561 }
562 }
563
564 world.frame ++;
565 }
566 }
567
568 void vg_render(void)
569 {
570 glViewport( 0,0, vg_window_x, vg_window_y );
571
572 glDisable( GL_DEPTH_TEST );
573 glClearColor( 0.01f, 0.01f, 0.01f, 1.0f );
574 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
575
576 glBindVertexArray( world.tile_vao );
577 SHADER_USE( shader_tile_colour );
578 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
579
580 for( int y = 0; y < world.h; y ++ )
581 {
582 for( int x = 0; x < world.w; x ++ )
583 {
584 glUniform2f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y );
585
586 v4f colour;
587
588 struct cell *cell = &world.data[y*world.w+x];
589
590 if( cell->state & FLAG_WALL ) { v4_copy( (v4f){ 0.2f, 0.2f, 0.2f, 1.0f }, colour ); }
591 else if( cell->state & FLAG_CANAL ) { v4_copy( (v4f){ 0.6f, 0.6f, 0.6f, 1.0f }, colour ); }
592 else if( cell->state & FLAG_INPUT ) { v4_copy( (v4f){ 0.2f, 0.3f, 0.7f, 1.0f }, colour ); }
593 else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.2f, 0.7f, 0.3f, 1.0f }, colour ); }
594 else v4_copy( (v4f){ 0.9f, 0.9f, 0.9f, 1.0f }, colour );
595
596 if( cell->water[world.frame&0x1] )
597 v4_copy( (v4f){ 0.2f, 0.3f, 0.7f * (float)(cell->water[world.frame&0x1]) * (1.0f/8.0f), 1.0f }, colour );
598
599 if( world.selected == y*world.w + x )
600 v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour );
601
602 //if( cell->state & (FLAG_SPLIT) )
603 // v4_copy( (v4f){ 0.75f, 0.75f, 0.02f, 1.0f }, colour );
604 //if( cell->state & (FLAG_MERGER) )
605 // v4_copy( (v4f){ 0.75f, 0.02f, 0.75f, 1.0f }, colour );
606
607 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour );
608
609 glDrawArrays( GL_TRIANGLES, 0, 6 );
610
611 if( cell->state & FLAG_CANAL )
612 {
613 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1.0f, 1.0f, 1.0f, 1.0f );
614 glDrawArrays( GL_TRIANGLES, 6, 3 );
615 }
616 }
617 }
618 }
619
620 void vg_ui(void){}