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