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