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