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