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