only rebuild map chunks where user clicks
[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 SHADER_DEFINE( shader_ball,
31 // VERTEX
32 "layout (location=0) in vec2 a_co;"
33 "uniform vec2 uOffset;"
34 "uniform mat3 uPv;"
35 ""
36 "out vec2 aTexCoords;"
37 ""
38 "void main()"
39 "{"
40 // Create texture coords
41 "aTexCoords = a_co;"
42
43 // Vertex transform
44 "vec3 worldpos = vec3( a_co * 0.5 - 0.25 + uOffset, 1.0 );"
45 "gl_Position = vec4( uPv * worldpos, 1.0 );"
46 "}",
47
48 // FRAGMENT
49 "out vec4 FragColor;"
50 ""
51 "uniform sampler2D uTexMain;"
52 "uniform vec3 uColour;"
53 ""
54 "in vec2 aTexCoords;"
55 ""
56 "void main()"
57 "{"
58 "vec4 glyph = texture( uTexMain, aTexCoords );"
59 "FragColor = vec4( uColour + glyph.rgb * 0.2, glyph.a );"
60 "}"
61 ,
62 UNIFORMS({ "uTexMain", "uColour", "uOffset", "uPv" })
63 )
64
65 SHADER_DEFINE( shader_tile_main,
66 // VERTEX
67 "layout (location=0) in vec2 a_co;"
68 "uniform vec4 uOffset;" // Tile x/y, uv x/y
69 "uniform mat3 uPv;"
70 "uniform mat2 uSubTransform;"
71 ""
72 "out vec4 aTexCoords;"
73 ""
74 "vec2 hash22(vec2 p)"
75 "{"
76 "vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));"
77 "p3 += dot(p3, p3.yzx+33.33);"
78 "return fract((p3.xx+p3.yz)*p3.zy);"
79 "}"
80 ""
81 "void main()"
82 "{"
83 // Create texture coords
84 "vec2 random_offset = floor(hash22(uOffset.xy) * 4.0) * 0.25;"
85 "vec2 edge_safe_coords = a_co * 0.98 + 0.01;"
86 "aTexCoords = vec4((edge_safe_coords + uOffset.zw) * 0.25, edge_safe_coords * 0.25 + random_offset );"
87
88 // Vertex transform
89 "vec2 subtransform = uSubTransform * (a_co-0.5) + 0.5;"
90 "vec3 worldpos = vec3( subtransform + uOffset.xy, 1.0 );"
91 "gl_Position = vec4( uPv * worldpos, 1.0 );"
92 "}",
93
94 // FRAGMENT
95 "out vec4 FragColor;"
96 ""
97 "uniform sampler2D uTexGlyphs;"
98 "uniform sampler2D uTexWood;"
99 ""
100 "in vec4 aTexCoords;"
101 ""
102 "void main()"
103 "{"
104 "vec3 shadowing_colour = vec3( 0.93, 0.88536, 0.8184 );"
105 "vec4 glyph = texture( uTexGlyphs, aTexCoords.xy );"
106 "vec4 wood = texture( uTexWood, aTexCoords.zw );"
107 "vec4 wood_secondary = texture( uTexWood, aTexCoords.zw + 0.25 );"
108 "vec3 wood_comp = mix( wood_secondary.rgb * shadowing_colour, wood.rgb, clamp( glyph.b * 2.0 - 1.0, 0.0, 1.0 ) );"
109
110 "vec3 shadows = mix( vec3( 0.85, 0.7344, 0.561 ), vec3(1.0,1.0,1.0), glyph.r );"
111
112 "FragColor = vec4( wood_comp * shadows, glyph.b );"
113 "}"
114 ,
115 UNIFORMS({ "uPv", "uOffset", "uTexGlyphs", "uTexWood", "uSubTransform" })
116 )
117
118 const char *level_pack[] =
119 {
120 // Level 0
121 "#########;\n"
122 "###-#####;acac\n"
123 "## ##;\n"
124 "## ##;\n"
125 "## ##;\n"
126 "## ##;\n"
127 "#####+###;acac\n"
128 "#########;\n",
129
130 // Level 1
131 "#########;\n"
132 "##-###-##;b,b\n"
133 "## ##;\n"
134 "## ##;\n"
135 "## ##;\n"
136 "## ##;\n"
137 "## ##;\n"
138 "####+####;bb\n"
139 "#########;\n",
140
141 // Level 2
142 "###########;\n"
143 "#####-#####;bbbbb\n"
144 "## ##;\n"
145 "## ###;\n"
146 "## # ##;\n"
147 "## ##;\n"
148 "###+##+####;bbb,bb\n"
149 "###########;\n",
150
151 // Level 3
152 "#############;\n"
153 "###-#####-###;a,aaa\n"
154 "## ##;\n"
155 "## ##;\n"
156 "## ##;\n"
157 "## ##;\n"
158 "## ##;\n"
159 "## ##;\n"
160 "######+######;aaaa\n"
161 "#############;\n",
162
163 // Level 4
164 "#############;\n"
165 "###-#####-###;aaa,aa\n"
166 "## ##;\n"
167 "## ##;\n"
168 "## ##;\n"
169 "## ##;\n"
170 "## ##;\n"
171 "## ##;\n"
172 "###+#####+###;aa,aaa\n"
173 "#############;\n"
174 };
175
176 GLuint tex_tile_data;
177 GLuint tex_tile_detail;
178 GLuint tex_wood;
179 GLuint tex_ball;
180
181 sfx_system_t audio_system_sfx =
182 {
183 .vol = 1.f,
184 .spd = 1.f,
185 .ch = 1,
186 .cur = 0,
187 .vol_src = &g_vol_sfx,
188 .flags = 0x00,
189 .fadeout = FADEOUT_LENGTH,
190 .name = "sfx"
191 };
192
193 sfx_set_t audio_tile_mod =
194 {
195 .sources = "\
196 sound/mod_01.ogg\0\
197 sound/mod_02.ogg\0\
198 sound/mod_03.ogg\0\
199 sound/mod_04.ogg\0\
200 sound/mod_05.ogg\0\
201 sound/mod_06.ogg\0",
202 .flags = 0
203 };
204
205 m3x3f m_projection;
206 m3x3f m_view;
207 m3x3f m_mdl;
208
209 #define FLAG_INPUT 0x1
210 #define FLAG_OUTPUT 0x2
211 #define FLAG_CANAL 0x4
212 #define FLAG_WALL 0x8
213 #define FLAG_FLIP_FLOP 0x100
214 #define FLAG_FLIP_ROTATING 0x200
215
216 /*
217 0000 0 | 0001 1 | 0010 2 | 0011 3
218 | | | | |
219 X | X= | X | X=
220 | | |
221 0100 4 | 0101 5 | 0110 6 | 0111 7
222 | | | | |
223 =X | =X= | =X | =X=
224 | | |
225 1000 8 | 1001 9 | 1010 10 | 1011 11
226 | | | | |
227 X | X= | X | X=
228 | | | | | | |
229 1100 12 | 1101 13 | 1110 14 | 1111 15
230 | | | | |
231 =X | =X= | =X | =X=
232 | | | | | | |
233 */
234
235 enum cell_type
236 {
237 k_cell_type_split = 7,
238 k_cell_type_merge = 13
239 };
240
241 v3f colour_sets[] =
242 { { 0.9f, 0.2f, 0.01f },
243 { 0.2f, 0.9f, 0.14f },
244 { 0.1f, 0.3f, 0.85f } };
245
246 static void colour_code_v3( char cc, v3f target )
247 {
248 if( cc >= 'a' && cc <= 'z' )
249 {
250 int id = cc - 'a';
251
252 if( id < vg_list_size( colour_sets ) )
253 {
254 v3_copy( colour_sets[ id ], target );
255 return;
256 }
257 }
258
259 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
260 }
261
262 struct mesh
263 {
264 GLuint vao, vbo;
265 u32 elements;
266 };
267
268 static void init_mesh( struct mesh *m, float *tris, u32 length )
269 {
270 m->elements = length/3;
271 glGenVertexArrays( 1, &m->vao );
272 glGenBuffers( 1, &m->vbo );
273
274 glBindVertexArray( m->vao );
275 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
276 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
277
278 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
279 glEnableVertexAttribArray( 0 );
280
281 VG_CHECK_GL();
282 }
283
284 static void free_mesh( struct mesh *m )
285 {
286 glDeleteVertexArrays( 1, &m->vao );
287 glDeleteBuffers( 1, &m->vbo );
288 }
289
290 static void draw_mesh( int const start, int const count )
291 {
292 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
293 }
294
295 static void use_mesh( struct mesh *m )
296 {
297 glBindVertexArray( m->vao );
298 }
299
300 struct world
301 {
302 struct cell
303 {
304 u32 state;
305 u8 config;
306 }
307 *data;
308
309 u32 frame;
310
311 u32 sim_frame;
312 float sim_start;
313 int simulating;
314
315 struct cell_terminal
316 {
317 // TODO: Split into input/output structures
318 char *conditions;
319 char recv[12];
320 int recv_count;
321 int id;
322 }
323 *io;
324
325 u32 w, h;
326
327 struct mesh tile, circle;
328
329 int selected;
330
331 struct fish
332 {
333 v2i pos;
334 v2i dir;
335 int alive;
336 char payload;
337 }
338 fishes[16];
339
340 int num_fishes;
341 } world = {};
342
343 static void map_free(void)
344 {
345 for( int i = 0; i < arrlen( world.io ); i ++ )
346 arrfree( world.io[ i ].conditions );
347
348 arrfree( world.data );
349 arrfree( world.io );
350
351 world.w = 0;
352 world.h = 0;
353 world.data = NULL;
354 world.io = NULL;
355 }
356
357 static void map_reclassify( v2i start, v2i end );
358 static int map_load( const char *str )
359 {
360 map_free();
361
362 char const *c = str;
363
364 // Scan for width
365 for(;; world.w ++)
366 {
367 if( str[world.w] == ';' )
368 break;
369 else if( !str[world.w] )
370 {
371 vg_error( "Unexpected EOF when parsing level\n" );
372 return 0;
373 }
374 }
375
376 struct cell *row = arraddnptr( world.data, world.w );
377 int cx = 0;
378 int reg_start = 0, reg_end = 0;
379
380 for(;;)
381 {
382 if( !*c )
383 break;
384
385 if( *c == ';' )
386 {
387 c ++;
388
389 // Parse attribs
390 if( *c != '\n' )
391 {
392 while( *c )
393 {
394 if( reg_start < reg_end )
395 {
396 if( *c >= 'a' && *c <= 'z' )
397 {
398 arrpush( world.io[ reg_start ].conditions, *c );
399 }
400 else
401 {
402 if( *c == ',' || *c == '\n' )
403 {
404 reg_start ++;
405
406 if( *c == '\n' )
407 break;
408 }
409 else
410 {
411 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
412 return 0;
413 }
414 }
415 }
416 else
417 {
418 vg_error( "Too many values to assign (row: %u)\n", world.h );
419 return 0;
420 }
421
422 c ++;
423 }
424 }
425
426 if( reg_start != reg_end )
427 {
428 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
429 return 0;
430 }
431
432 if( cx != world.w )
433 {
434 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
435 return 0;
436 }
437
438 row = arraddnptr( world.data, world.w );
439 cx = 0;
440 world.h ++;
441 reg_end = reg_start = arrlen( world.io );
442 }
443 else
444 {
445 if( cx == world.w )
446 {
447 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
448 return 0;
449 }
450
451 // Tile initialization
452 // row[ cx ] .. etc
453
454 if( *c == '+' || *c == '-' )
455 {
456 struct cell_terminal term = { .id = cx + world.h*world.w };
457 arrpush( world.io, term );
458 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
459 reg_end ++;
460 }
461 else if( *c == '#' )
462 {
463 row[ cx ++ ].state = FLAG_WALL;
464 }
465 else
466 {
467 row[ cx ++ ].state = 0x00;
468 }
469 }
470
471 c ++;
472 }
473
474 map_reclassify( NULL, NULL );
475 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
476 return 1;
477 }
478
479 static struct cell *pcell( v2i pos )
480 {
481 return &world.data[ pos[1]*world.w + pos[0] ];
482 }
483
484 int main( int argc, char *argv[] )
485 {
486 vg_init( argc, argv, "Fish (Marbles Computer) Ladder Simulator 2022 | N,M: change level | SPACE: Test | LeftClick: Toggle tile" );
487 }
488
489 void vg_register(void)
490 {
491 SHADER_INIT( shader_tile_colour );
492 SHADER_INIT( shader_tile_main );
493 SHADER_INIT( shader_ball );
494 }
495
496 void vg_start(void)
497 {
498 // Quad mesh
499 {
500 float quad_mesh[] =
501 {
502 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
503 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
504
505 0.48f, 0.48f, 0.5f, 0.52f, 0.52f, 0.52f, // Static dot
506 0.375f, 0.25f, 0.5f, 0.75f, 0.625f, 0.25f, // Downwards pointing arrow
507 0.25f, 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, // Left
508 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, 0.75f, // up
509 0.75f, 0.375f, 0.25f, 0.5f, 0.75f, 0.625f
510 };
511
512 init_mesh( &world.tile, quad_mesh, vg_list_size(quad_mesh) );
513 }
514
515 // Circle mesh
516 {
517 float circle_mesh[32*6*3];
518 int res = vg_list_size( circle_mesh ) / (6*3);
519
520 for( int i = 0; i < res; i ++ )
521 {
522 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
523 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
524
525 circle_mesh[ i*6+0 ] = 0.0f;
526 circle_mesh[ i*6+1 ] = 0.0f;
527
528 v2_copy( v0, circle_mesh + 32*6 + i*12 );
529 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
530 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
531
532 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
533 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
534 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
535
536 v2_copy( v0, circle_mesh + i*6+4 );
537 v2_copy( v1, circle_mesh + i*6+2 );
538 v2_copy( v0, circle_mesh+i*6+4 );
539 v2_copy( v1, circle_mesh+i*6+2 );
540 }
541
542 init_mesh( &world.circle, circle_mesh, vg_list_size( circle_mesh ) );
543 }
544
545 // Textures
546 {
547 tex_tile_detail = vg_tex2d_rgba( "textures/tile_overlays.png" );
548 vg_tex2d_mipmap();
549 vg_tex2d_linear_mipmap();
550 vg_tex2d_repeat();
551
552 tex_tile_data = vg_tex2d_rgba( "textures/tileset.png" );
553 vg_tex2d_mipmap();
554 vg_tex2d_linear_mipmap();
555 vg_tex2d_repeat();
556
557 tex_wood = vg_tex2d_rgba( "textures/wood.png" );
558 vg_tex2d_mipmap();
559 vg_tex2d_linear_mipmap();
560 vg_tex2d_repeat();
561
562 tex_ball = vg_tex2d_rgba( "textures/ball.png" );
563 vg_tex2d_mipmap();
564 vg_tex2d_linear_mipmap();
565 vg_tex2d_clamp();
566 }
567
568 // Audio
569 {
570 sfx_set_init( &audio_tile_mod, NULL );
571 }
572
573 map_load( level_pack[ 0 ] );
574 }
575
576 void vg_free(void)
577 {
578 free_mesh( &world.tile );
579 free_mesh( &world.circle );
580
581 map_free();
582
583 glDeleteTextures( 1, &tex_tile_data );
584 glDeleteTextures( 1, &tex_tile_detail );
585 glDeleteTextures( 1, &tex_wood );
586 glDeleteTextures( 1, &tex_ball );
587
588 sfx_set_free( &audio_tile_mod );
589 }
590
591 static int cell_interactive( v2i co )
592 {
593 // Bounds check
594 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
595 return 0;
596
597 // Flags check
598 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
599 return 0;
600
601 // List of 3x3 configurations that we do not allow
602 static u32 invalid_src[][9] =
603 {
604 { 0,1,0,
605 1,1,1,
606 0,1,0
607 },
608 { 0,0,0,
609 0,1,1,
610 0,1,1
611 },
612 { 0,0,0,
613 1,1,0,
614 1,1,0
615 },
616 { 0,1,1,
617 0,1,1,
618 0,0,0
619 },
620 { 1,1,0,
621 1,1,0,
622 0,0,0
623 },
624 { 0,1,0,
625 0,1,1,
626 0,1,0
627 },
628 { 0,1,0,
629 1,1,0,
630 0,1,0
631 }
632 };
633
634 // Statically compile invalid configurations into bitmasks
635 static u32 invalid[ vg_list_size(invalid_src) ];
636
637 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
638 {
639 u32 comped = 0x00;
640
641 for( int j = 0; j < 3; j ++ )
642 for( int k = 0; k < 3; k ++ )
643 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
644
645 invalid[i] = comped;
646 }
647
648 // Extract 5x5 grid surrounding tile
649 u32 blob = 0x1000;
650 for( int y = co[1]-2; y < co[1]+3; y ++ )
651 for( int x = co[0]-2; x < co[0]+3; x ++ )
652 {
653 struct cell *cell = pcell((v2i){x,y});
654
655 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
656 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
657 }
658
659 // Run filter over center 3x3 grid to check for invalid configurations
660 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
661 for( int i = 0; i < vg_list_size(kernel); i ++ )
662 {
663 if( blob & (0x1 << (6+kernel[i])) )
664 {
665 u32 window = blob >> kernel[i];
666
667 for( int j = 0; j < vg_list_size(invalid); j ++ )
668 if((window & invalid[j]) == invalid[j])
669 return 0;
670 }
671 }
672
673 return 1;
674 }
675
676 static void map_reclassify( v2i start, v2i end )
677 {
678 v2i full_start = { 2,2 };
679 v2i full_end = { world.w-2, world.h-2 };
680
681 if( !start || !end )
682 {
683 start = full_start;
684 end = full_end;
685 }
686
687 for( int y = vg_max( start[1], full_start[1] ); y < vg_min( end[1], full_end[1] ); y ++ )
688 {
689 for( int x = vg_max( start[0], full_start[0] ); x < vg_min( end[0], full_end[0] ); x ++ )
690 {
691 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
692
693 u8 config = 0x00;
694
695 if( pcell((v2i){x,y})->state & FLAG_CANAL )
696 {
697 for( int i = 0; i < vg_list_size( dirs ); i ++ )
698 {
699 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
700 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
701 config |= 0x1 << i;
702 }
703 }
704 else config = 0xF;
705
706 pcell((v2i){x,y})->config = config;
707 }
708 }
709 }
710
711 void vg_update(void)
712 {
713 static int curlevel = 0;
714 int changelvl = curlevel;
715 if( vg_get_button_down( "prev" ) ) { if( curlevel > 0 ) changelvl --; }
716 else if( vg_get_button_down( "next" ) ) { if( curlevel < vg_list_size( level_pack )-1 ) changelvl ++; }
717
718 if( changelvl != curlevel )
719 {
720 map_load( level_pack[ changelvl ] );
721 curlevel = changelvl;
722
723 // TEMP!!! code dupe
724 world.simulating = 0;
725 world.num_fishes = 0;
726 world.sim_frame = 0;
727
728 for( int i = 0; i < arrlen( world.io ); i ++ )
729 world.io[i].recv_count = 0;
730
731 vg_info( "Stopping simulation!\n" );
732 }
733
734 float ratio = (float)vg_window_y / (float)vg_window_x;
735 float const size = 9.5f;
736
737 v3f origin;
738 origin[0] = -0.5f * world.w;
739 origin[1] = -0.5f * world.h;
740 origin[2] = 0.0f;
741
742 m3x3_projection( m_projection, -size, size, -size*ratio, size*ratio );
743 m3x3_identity( m_view );
744 m3x3_translate( m_view, origin );
745 m3x3_mul( m_projection, m_view, vg_pv );
746 vg_projection_update();
747
748 // Input stuff
749
750 v2f tile_pos;
751 v2_copy( vg_mouse_ws, tile_pos );
752
753 int tile_x = floorf( tile_pos[0] );
754 int tile_y = floorf( tile_pos[1] );
755
756 // Tilemap editing
757 if( !world.simulating )
758 {
759 if( cell_interactive( (v2i){ tile_x, tile_y } ))
760 {
761 world.selected = tile_y * world.w + tile_x;
762
763 if( vg_get_button_down("primary") )
764 {
765 world.data[ world.selected ].state ^= FLAG_CANAL;
766
767 if( world.data[ world.selected ].state & FLAG_CANAL )
768 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
769 else
770 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
771
772 map_reclassify( (v2i){ tile_x -2, tile_y -2 }, (v2i){ tile_x +2, tile_y +2 } );
773 }
774 }
775 else
776 world.selected = -1;
777 }
778
779 // Simulation stop/start
780 if( vg_get_button_down("go") )
781 {
782 if( world.simulating )
783 {
784 world.simulating = 0;
785 world.num_fishes = 0;
786 world.sim_frame = 0;
787
788 for( int i = 0; i < arrlen( world.io ); i ++ )
789 world.io[i].recv_count = 0;
790
791 vg_info( "Stopping simulation!\n" );
792 }
793 else
794 {
795 vg_success( "Starting simulation!\n" );
796
797 world.simulating = 1;
798 world.num_fishes = 0;
799 world.sim_frame = 0;
800 world.sim_start = vg_time;
801
802 for( int i = 0; i < world.w*world.h; i ++ )
803 {
804 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
805 }
806
807 for( int i = 0; i < arrlen( world.io ); i ++ )
808 world.io[i].recv_count = 0;
809 }
810 }
811
812 // Fish ticks
813 if( world.simulating )
814 {
815 while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) )
816 {
817 vg_info( "frame: %u\n", world.sim_frame );
818
819 for( int i = 0; i < arrlen( world.io ); i ++ )
820 {
821 struct cell_terminal *term = &world.io[ i ];
822 int posx = term->id % world.w;
823 int posy = (term->id - posx)/world.w;
824 int is_input = world.data[ term->id ].state & FLAG_INPUT;
825
826 if( is_input )
827 {
828 if( world.sim_frame < arrlen( term->conditions ) )
829 {
830 struct fish *fish = &world.fishes[world.num_fishes++];
831 fish->pos[0] = posx;
832 fish->pos[1] = posy;
833 fish->alive = 1;
834 fish->payload = term->conditions[world.sim_frame];
835
836 int can_spawn = 0;
837
838 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
839 for( int j = 0; j < vg_list_size(dirs); j ++ )
840 if( pcell( (v2i){ posx+dirs[j][0], posy+dirs[j][1] } )->state & FLAG_CANAL )
841 {
842 fish->dir[0] = dirs[j][0];
843 fish->dir[1] = dirs[j][1];
844 can_spawn = 1;
845 break;
846 }
847
848 if( !can_spawn )
849 world.num_fishes--;
850 }
851 }
852 }
853
854 // Update splitter deltas
855 for( int i = 0; i < world.h*world.w; i ++ )
856 {
857 struct cell *cell = &world.data[i];
858 if( cell->config == k_cell_type_split )
859 {
860 cell->state &= ~FLAG_FLIP_ROTATING;
861 }
862 }
863
864 for( int i = 0; i < world.num_fishes; i ++ )
865 {
866 struct fish *fish = &world.fishes[i];
867 struct cell *cell_current = pcell( fish->pos );
868
869 if( !fish->alive )
870 continue;
871
872 // Apply to output
873 if( cell_current->state & FLAG_OUTPUT )
874 {
875 for( int j = 0; j < arrlen( world.io ); j ++ )
876 {
877 struct cell_terminal *term = &world.io[j];
878
879 if( term->id == fish->pos[1]*world.w + fish->pos[0] )
880 {
881 term->recv[ term->recv_count ++ ] = fish->payload;
882 break;
883 }
884 }
885
886 fish->alive = 0;
887 continue;
888 }
889
890 if( cell_current->config == k_cell_type_split )
891 {
892 // Flip flop L/R
893 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
894 fish->dir[1] = 0;
895
896 cell_current->state ^= FLAG_FLIP_FLOP;
897 }
898 else if( cell_current->config == k_cell_type_merge )
899 {
900 // Can only move up
901 fish->dir[0] = 0;
902 fish->dir[1] = -1;
903 }
904 else
905 {
906 struct cell *cell_next = pcell( (v2i){ fish->pos[0]+fish->dir[0], fish->pos[1]+fish->dir[1] } );
907 if( !(cell_next->state & (FLAG_CANAL|FLAG_OUTPUT)) )
908 {
909 // Try other directions for valid, so down, left, right..
910 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
911 vg_info( "Trying some other directions...\n" );
912
913 for( int j = 0; j < vg_list_size(dirs); j ++ )
914 {
915 if( (dirs[j][0] == -fish->dir[0]) && (dirs[j][1] == -fish->dir[1]) )
916 continue;
917
918 if( pcell( (v2i){ fish->pos[0]+dirs[j][0], fish->pos[1]+dirs[j][1] } )->state & (FLAG_CANAL|FLAG_OUTPUT) )
919 {
920 fish->dir[0] = dirs[j][0];
921 fish->dir[1] = dirs[j][1];
922 }
923 }
924 }
925 }
926
927 fish->pos[0] += fish->dir[0];
928 fish->pos[1] += fish->dir[1];
929
930 struct cell *cell_entry = pcell( fish->pos );
931
932 if( !(cell_entry->state & (FLAG_INPUT|FLAG_CANAL|FLAG_OUTPUT) ))
933 fish->alive = 0;
934 else
935 if( cell_entry->config == k_cell_type_split )
936 cell_entry->state |= FLAG_FLIP_ROTATING;
937 }
938
939 world.sim_frame ++;
940 }
941 }
942 }
943
944 static void render_tiles(void)
945 {
946 for( int y = 0; y < world.h; y ++ )
947 {
948 for( int x = 0; x < world.w; x ++ )
949 {
950 struct cell *cell = pcell((v2i){x,y});
951 int selected = world.selected == y*world.w + x;
952
953 int tile_offsets[][2] =
954 {
955 {2, 0}, {0, 3}, {0, 2}, {2, 2},
956 {1, 0}, {2, 3}, {3, 2}, {1, 3},
957 {3, 1}, {0, 1}, {1, 2}, {2, 1},
958 {1, 1}, {3, 3}, {2, 1}, {2, 1}
959 };
960
961 int uv[2] = { 3, 0 };
962
963 if( cell->state & FLAG_CANAL )
964 {
965 uv[0] = tile_offsets[ cell->config ][0];
966 uv[1] = tile_offsets[ cell->config ][1];
967 }
968
969 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
970 draw_mesh( 0, 2 );
971 }
972 }
973 }
974
975 void vg_render(void)
976 {
977 glViewport( 0,0, vg_window_x, vg_window_y );
978
979 glDisable( GL_DEPTH_TEST );
980 glClearColor( 0.8f, 0.8f, 0.8f, 1.0f );
981 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
982
983 float scaled_time = 0.0f, frame_lerp = 0.0f;
984
985 if( world.simulating )
986 {
987 scaled_time = (vg_time-world.sim_start)*2.0f;
988 frame_lerp = scaled_time - (float)world.sim_frame;
989 }
990
991 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
992 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
993 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
994 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
995
996 v2f const curve_7[] = {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
997 v2f const curve_7_1[] = {{0.5f,0.8438f},{1.0f-0.875f,0.8438f},{1.0-0.625f,0.5f},{0.0f,0.5f}};
998
999 float const curve_7_linear_section = 0.1562f;
1000
1001 // TILE SET RENDERING
1002 // todo: just slam everything into a mesh...
1003 // when user modifies a tile the neighbours can be easily uploaded to gpu mem
1004 // in ~3 subBuffers
1005 // Currently we're uploading a fair amount of data every frame anyway.
1006 // NOTE: this is for final optimisations ONLY!
1007 // ======================================================================
1008
1009 use_mesh( &world.tile );
1010
1011 SHADER_USE( shader_tile_main );
1012
1013 m2x2f subtransform;
1014 m2x2_identity( subtransform );
1015 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1016 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1017
1018 // Bind textures
1019 glActiveTexture( GL_TEXTURE0 );
1020 glBindTexture( GL_TEXTURE_2D, tex_tile_data );
1021 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
1022
1023 glActiveTexture( GL_TEXTURE1 );
1024 glBindTexture( GL_TEXTURE_2D, tex_wood );
1025 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
1026
1027 render_tiles();
1028
1029 glEnable(GL_BLEND);
1030 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1031 glBlendEquation(GL_FUNC_ADD);
1032
1033 SHADER_USE( shader_ball );
1034 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1035
1036 glActiveTexture( GL_TEXTURE0 );
1037 glBindTexture( GL_TEXTURE_2D, tex_ball );
1038 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
1039
1040 // Draw 'fish'
1041 if( world.simulating )
1042 {
1043 for( int i = 0; i < world.num_fishes; i ++ )
1044 {
1045 struct fish *fish = &world.fishes[i];
1046
1047 if( !fish->alive )
1048 continue;
1049
1050 // Evaluate position
1051 struct cell *cell = pcell(fish->pos);
1052 v2f fish_pos;
1053
1054 v2f const *curve;
1055
1056 float t = frame_lerp;
1057
1058 switch( cell->config )
1059 {
1060 case 13:
1061 if( fish->dir[0] == 1 )
1062 curve = curve_12;
1063 else
1064 curve = curve_9;
1065 break;
1066 case 3: curve = curve_3; break;
1067 case 6: curve = curve_6; break;
1068 case 9: curve = curve_9; break;
1069 case 12: curve = curve_12; break;
1070 case 7:
1071 if( t > curve_7_linear_section )
1072 {
1073 t -= curve_7_linear_section;
1074 t *= (1.0f/(1.0f-curve_7_linear_section));
1075
1076 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
1077 }
1078 else curve = NULL;
1079 break;
1080 default: curve = NULL; break;
1081 }
1082
1083 if( curve )
1084 {
1085 float t2 = t * t;
1086 float t3 = t * t * t;
1087
1088 float cA = 3.0f*t2 - 3.0f*t3;
1089 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
1090 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
1091
1092 fish_pos[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
1093 fish_pos[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
1094 fish_pos[0] += (float)fish->pos[0];
1095 fish_pos[1] += (float)fish->pos[1];
1096 }
1097 else
1098 {
1099 v2f origin;
1100 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
1101 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
1102
1103 fish_pos[0] = origin[0] + (float)fish->dir[0]*t;
1104 fish_pos[1] = origin[1] + (float)fish->dir[1]*t;
1105 }
1106
1107 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
1108 colour_code_v3( fish->payload, dot_colour );
1109
1110 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
1111 glUniform2f( SHADER_UNIFORM( shader_ball, "uOffset" ), fish_pos[0], fish_pos[1] );
1112 draw_mesh( 0, 32 );
1113 }
1114 }
1115
1116
1117 SHADER_USE( shader_tile_main );
1118
1119 // Bind textures
1120 glActiveTexture( GL_TEXTURE0 );
1121 glBindTexture( GL_TEXTURE_2D, tex_tile_data );
1122 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
1123
1124 glActiveTexture( GL_TEXTURE1 );
1125 glBindTexture( GL_TEXTURE_2D, tex_wood );
1126 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
1127
1128 render_tiles();
1129
1130 // Draw splitters
1131
1132 for( int y = 0; y < world.h; y ++ )
1133 {
1134 for( int x = 0; x < world.w; x ++ )
1135 {
1136 struct cell *cell = pcell((v2i){x,y});
1137
1138 if( cell->config == k_cell_type_split )
1139 {
1140 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
1141
1142 if( cell->state & FLAG_FLIP_ROTATING )
1143 {
1144 if( (frame_lerp > curve_7_linear_section) )
1145 {
1146 float const rotation_speed = 0.4f;
1147 if( (frame_lerp < 1.0f-rotation_speed) )
1148 {
1149 float t = frame_lerp - curve_7_linear_section;
1150 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
1151 t += 1.0f;
1152
1153 rotation *= t;
1154 }
1155 else
1156 rotation *= -1.0f;
1157 }
1158 }
1159
1160 m2x2_create_rotation( subtransform, rotation );
1161
1162 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1163 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y + 0.125f, 0.0f, 0.0f );
1164 draw_mesh( 0, 2 );
1165 }
1166 }
1167 }
1168
1169 //glDisable(GL_BLEND);
1170
1171 glDisable(GL_BLEND);
1172
1173 SHADER_USE( shader_tile_colour );
1174 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1175 use_mesh( &world.circle );
1176
1177 // Draw i/o arrays
1178 for( int i = 0; i < arrlen( world.io ); i ++ )
1179 {
1180 struct cell_terminal *term = &world.io[ i ];
1181 int posx = term->id % world.w;
1182 int posy = (term->id - posx)/world.w;
1183 int is_input = world.data[ term->id ].state & FLAG_INPUT;
1184
1185 int const filled_start = 0;
1186 int const filled_count = 32;
1187 int const empty_start = 32;
1188 int const empty_count = 32*2;
1189
1190 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
1191
1192 for( int j = 0; j < arrlen( term->conditions ); j ++ )
1193 {
1194 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)posx + 0.2f + 0.2f * (float)j, (float)posy + 0.2f, 0.1f );
1195
1196 if( is_input )
1197 {
1198 colour_code_v3( term->conditions[j], dot_colour );
1199 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1200
1201 // Draw filled if tick not passed, draw empty if empty
1202 if( world.sim_frame > j )
1203 draw_mesh( empty_start, empty_count );
1204 else
1205 draw_mesh( filled_start, filled_count );
1206 }
1207 else
1208 {
1209 if( term->recv_count > j )
1210 {
1211 colour_code_v3( term->recv[j], dot_colour );
1212 v3_muls( dot_colour, 0.8f, dot_colour );
1213 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1214
1215 draw_mesh( filled_start, filled_count );
1216 }
1217
1218 colour_code_v3( term->conditions[j], dot_colour );
1219 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1220
1221 draw_mesh( empty_start, empty_count );
1222 }
1223 }
1224 }
1225
1226 if( world.simulating )
1227 {
1228 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.0f, 0.0f, 0.0f, 1.0f );
1229 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), -0.5f + cosf( vg_time * 4.0f ) * 0.2f, sinf( vg_time * 4.0f ) * 0.2f + (float)world.h * 0.5f, 0.05f );
1230 draw_mesh( 0, 32 );
1231 }
1232 }
1233
1234 void vg_ui(void)
1235 {
1236 //ui_test();
1237 }