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