better console handling // simple map serialzer
[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
340 if( *c == '+' || *c == '-' )
341 {
342 struct cell_terminal term = { .id = cx + world.h*world.w };
343 arrpush( world.io, term );
344 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
345 reg_end ++;
346 }
347 else if( *c == '#' )
348 {
349 row[ cx ++ ].state = FLAG_WALL;
350 }
351 else
352 {
353 row[ cx ++ ].state = 0x00;
354 }
355 }
356
357 c ++;
358 }
359
360 // Update data texture to fill out the background
361 {
362 u8 info_buffer[64*64*4];
363 for( int i = 0; i < 64*64; i ++ )
364 {
365 u8 *px = &info_buffer[i*4];
366 px[0] = 255;
367 px[1] = 0;
368 px[2] = 0;
369 px[3] = 0;
370 }
371
372 glBindTexture( GL_TEXTURE_2D, world.background_data );
373 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
374 }
375
376 map_reclassify( NULL, NULL, 1 );
377 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
378 return 1;
379 }
380
381 static struct cell *pcell( v2i pos )
382 {
383 return &world.data[ pos[1]*world.w + pos[0] ];
384 }
385
386 static void map_serialize( FILE *stream )
387 {
388 for( int y = 0; y < world.h; y ++ )
389 {
390 for( int x = 0; x < world.w; x ++ )
391 {
392 struct cell *cell = pcell( (v2i){ x, y } );
393
394 if( cell->state & FLAG_WALL ) fputc( '#', stream );
395 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
396 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
397 else if( cell->state & FLAG_CANAL ) fputc( '*', stream );
398 else fputc( ' ', stream );
399 }
400
401 fputc( ';', stream );
402
403 int terminal_write_count = 0;
404
405 for( int x = 0; x < world.w; x ++ )
406 {
407 for( int i = 0; i < arrlen( world.io ); i ++ )
408 {
409 struct cell_terminal *term = &world.io[i];
410 if( term->id == y*world.w+x )
411 {
412 if( terminal_write_count )
413 fputc( ',', stream );
414 terminal_write_count ++;
415
416 for( int j = 0; j < arrlen( term->conditions ); j ++ )
417 fputc( term->conditions[j], stream );
418 }
419 }
420 }
421
422 fputc( '\n', stream );
423 }
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 static void console_save_map( int argc, char *argv[] )
432 {
433 if( argc >= 1 )
434 {
435 FILE *test_writer = fopen( argv[0], "w" );
436 if( test_writer )
437 {
438 map_serialize( test_writer );
439
440 fclose( test_writer );
441 }
442 }
443 }
444
445 void vg_start(void)
446 {
447 vg_function_push( (struct vg_cmd){
448 .name = "save_map",
449 .function = console_save_map
450 });
451
452 // Quad mesh
453 {
454 float quad_mesh[] =
455 {
456 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
457 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
458
459 0.48f, 0.48f, 0.5f, 0.52f, 0.52f, 0.52f, // Static dot
460 0.375f, 0.25f, 0.5f, 0.75f, 0.625f, 0.25f, // Downwards pointing arrow
461 0.25f, 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, // Left
462 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, 0.75f, // up
463 0.75f, 0.375f, 0.25f, 0.5f, 0.75f, 0.625f
464 };
465
466 init_mesh( &world.tile, quad_mesh, vg_list_size(quad_mesh) );
467 }
468
469 // Circle mesh
470 {
471 float circle_mesh[32*6*3];
472 int res = vg_list_size( circle_mesh ) / (6*3);
473
474 for( int i = 0; i < res; i ++ )
475 {
476 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
477 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
478
479 circle_mesh[ i*6+0 ] = 0.0f;
480 circle_mesh[ i*6+1 ] = 0.0f;
481
482 v2_copy( v0, circle_mesh + 32*6 + i*12 );
483 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
484 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
485
486 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
487 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
488 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
489
490 v2_copy( v0, circle_mesh + i*6+4 );
491 v2_copy( v1, circle_mesh + i*6+2 );
492 v2_copy( v0, circle_mesh+i*6+4 );
493 v2_copy( v1, circle_mesh+i*6+2 );
494 }
495
496 init_mesh( &world.circle, circle_mesh, vg_list_size( circle_mesh ) );
497 }
498
499 // Create info data texture
500 {
501 glGenTextures( 1, &world.background_data );
502 glBindTexture( GL_TEXTURE_2D, world.background_data );
503 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
504 vg_tex2d_nearest();
505 }
506
507 // Create random smaples texture
508 {
509 u8 *data = malloc(512*512*2);
510 for( int i = 0; i < 512*512*2; i ++ )
511 data[ i ] = rand()/(RAND_MAX/255);
512
513 glGenTextures( 1, &world.random_samples );
514 glBindTexture( GL_TEXTURE_2D, world.random_samples );
515 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
516 vg_tex2d_linear();
517 vg_tex2d_repeat();
518
519 free( data );
520 }
521
522 resource_load_main();
523
524 map_load( level_pack[ 0 ] );
525 }
526
527 void vg_free(void)
528 {
529 resource_free_main();
530
531 glDeleteTextures( 1, &world.background_data );
532 glDeleteTextures( 1, &world.random_samples );
533
534 free_mesh( &world.tile );
535 free_mesh( &world.circle );
536
537 map_free();
538 }
539
540 static int cell_interactive( v2i co )
541 {
542 // Bounds check
543 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
544 return 0;
545
546 // Flags check
547 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
548 return 0;
549
550 // List of 3x3 configurations that we do not allow
551 static u32 invalid_src[][9] =
552 {
553 { 0,1,0,
554 1,1,1,
555 0,1,0
556 },
557 { 0,0,0,
558 0,1,1,
559 0,1,1
560 },
561 { 0,0,0,
562 1,1,0,
563 1,1,0
564 },
565 { 0,1,1,
566 0,1,1,
567 0,0,0
568 },
569 { 1,1,0,
570 1,1,0,
571 0,0,0
572 },
573 { 0,1,0,
574 0,1,1,
575 0,1,0
576 },
577 { 0,1,0,
578 1,1,0,
579 0,1,0
580 }
581 };
582
583 // Statically compile invalid configurations into bitmasks
584 static u32 invalid[ vg_list_size(invalid_src) ];
585
586 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
587 {
588 u32 comped = 0x00;
589
590 for( int j = 0; j < 3; j ++ )
591 for( int k = 0; k < 3; k ++ )
592 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
593
594 invalid[i] = comped;
595 }
596
597 // Extract 5x5 grid surrounding tile
598 u32 blob = 0x1000;
599 for( int y = co[1]-2; y < co[1]+3; y ++ )
600 for( int x = co[0]-2; x < co[0]+3; x ++ )
601 {
602 struct cell *cell = pcell((v2i){x,y});
603
604 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
605 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
606 }
607
608 // Run filter over center 3x3 grid to check for invalid configurations
609 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
610 for( int i = 0; i < vg_list_size(kernel); i ++ )
611 {
612 if( blob & (0x1 << (6+kernel[i])) )
613 {
614 u32 window = blob >> kernel[i];
615
616 for( int j = 0; j < vg_list_size(invalid); j ++ )
617 if((window & invalid[j]) == invalid[j])
618 return 0;
619 }
620 }
621
622 return 1;
623 }
624
625 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
626 {
627 v2i full_start = { 1,1 };
628 v2i full_end = { world.w-1, world.h-1 };
629
630 if( !start || !end )
631 {
632 start = full_start;
633 end = full_end;
634 }
635
636 // Texture data
637 u8 info_buffer[64*64*4];
638 u32 pixel_id = 0;
639
640 int px0 = vg_max( start[0], full_start[0] ),
641 px1 = vg_min( end[0], full_end[0] ),
642 py0 = vg_max( start[1], full_start[1] ),
643 py1 = vg_min( end[1], full_end[1] );
644
645 for( int y = py0; y < py1; y ++ )
646 {
647 for( int x = px0; x < px1; x ++ )
648 {
649 struct cell *cell = pcell((v2i){x,y});
650
651 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
652
653 u8 height = 0;
654 u8 config = 0x00;
655
656 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
657 {
658 for( int i = 0; i < vg_list_size( dirs ); i ++ )
659 {
660 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
661 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
662 config |= 0x1 << i;
663 }
664
665 height = 128;
666 }
667 else
668 {
669 if( cell->state & FLAG_WALL )
670 height = 255;
671
672 config = 0xF;
673 }
674
675 pcell((v2i){x,y})->config = config;
676
677 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
678 info_px[0] = height;
679 info_px[1] = cell->state & FLAG_WALL? 0: 255;
680 info_px[2] = 0;
681 info_px[3] = 0;
682 }
683 }
684
685 if( update_texbuffer )
686 {
687 glBindTexture( GL_TEXTURE_2D, world.background_data );
688 glTexSubImage2D( GL_TEXTURE_2D, 0, px0 + 16, py0 + 16, px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
689 }
690 }
691
692 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
693 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
694 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
695 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
696
697 v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}};
698 v2f const curve_8[] = {{0.5f,0.8f},{0.5f,0.5f},{0.5f,0.3f},{0.5f,0.0f}};
699
700 v2f const curve_7[] = {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
701 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}};
702
703 float const curve_7_linear_section = 0.1562f;
704
705 void vg_update(void)
706 {
707 static int curlevel = 0;
708 int changelvl = curlevel;
709 if( vg_get_button_down( "prev" ) ) { if( curlevel > 0 ) changelvl --; }
710 else if( vg_get_button_down( "next" ) ) { if( curlevel < vg_list_size( level_pack )-1 ) changelvl ++; }
711
712 if( changelvl != curlevel )
713 {
714 map_load( level_pack[ changelvl ] );
715 curlevel = changelvl;
716
717 // TEMP!!! code dupe
718 world.simulating = 0;
719 world.num_fishes = 0;
720 world.sim_frame = 0;
721
722 for( int i = 0; i < arrlen( world.io ); i ++ )
723 world.io[i].recv_count = 0;
724
725 vg_info( "Stopping simulation!\n" );
726 }
727
728 // Fit within screen
729
730 float r1 = (float)vg_window_y / (float)vg_window_x,
731 r2 = (float)world.h / (float)world.w,
732 size;
733
734 size = ( r2 < r1? (float)world.w * 0.5f: ((float)world.h * 0.5f) / r1 ) + 2.5f;
735 m3x3_projection( m_projection, -size, size, -size*r1, size*r1 );
736
737 v3f origin;
738 origin[0] = floorf( -0.5f * world.w );
739 origin[1] = floorf( -0.5f * world.h );
740 origin[2] = 0.0f;
741
742 m3x3_identity( m_view );
743 m3x3_translate( m_view, origin );
744 m3x3_mul( m_projection, m_view, vg_pv );
745 vg_projection_update();
746
747 // Input stuff
748 v2_copy( vg_mouse_ws, world.tile_pos );
749
750 world.tile_x = floorf( world.tile_pos[0] );
751 world.tile_y = floorf( world.tile_pos[1] );
752
753 // Tilemap editing
754 if( !world.simulating )
755 {
756 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
757 {
758 world.selected = world.tile_y * world.w + world.tile_x;
759
760 if( vg_get_button_down("primary") )
761 {
762 world.data[ world.selected ].state ^= FLAG_CANAL;
763
764 if( world.data[ world.selected ].state & FLAG_CANAL )
765 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
766 else
767 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
768
769 map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 },
770 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
771 }
772 }
773 else
774 world.selected = -1;
775 }
776 else world.selected = -1;
777
778 // Simulation stop/start
779 if( vg_get_button_down("go") )
780 {
781 if( world.simulating )
782 {
783 world.simulating = 0;
784 world.num_fishes = 0;
785 world.sim_frame = 0;
786
787 for( int i = 0; i < arrlen( world.io ); i ++ )
788 world.io[i].recv_count = 0;
789
790 vg_info( "Stopping simulation!\n" );
791
792 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
793 }
794 else
795 {
796 vg_success( "Starting simulation!\n" );
797
798 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
799
800 world.simulating = 1;
801 world.num_fishes = 0;
802 world.sim_frame = 0;
803 world.sim_start = vg_time;
804
805 for( int i = 0; i < world.w*world.h; i ++ )
806 {
807 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
808 }
809
810 for( int i = 0; i < arrlen( world.io ); i ++ )
811 world.io[i].recv_count = 0;
812 }
813 }
814
815 // Fish ticks
816 if( world.simulating )
817 {
818 while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) )
819 {
820 //vg_info( "frame: %u\n", world.sim_frame );
821 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 9 );
822
823 // Update splitter deltas
824 for( int i = 0; i < world.h*world.w; i ++ )
825 {
826 struct cell *cell = &world.data[i];
827 if( cell->config == k_cell_type_split )
828 {
829 cell->state &= ~FLAG_FLIP_ROTATING;
830 }
831 }
832
833 // Update fish positions
834 for( int i = 0; i < world.num_fishes; i ++ )
835 {
836 struct fish *fish = &world.fishes[i];
837 struct cell *cell_current = pcell( fish->pos );
838
839 if( fish->alive == -1 )
840 fish->alive = 0;
841
842 if( fish->alive != 1 )
843 continue;
844
845 // Apply to output
846 if( cell_current->state & FLAG_OUTPUT )
847 {
848 for( int j = 0; j < arrlen( world.io ); j ++ )
849 {
850 struct cell_terminal *term = &world.io[j];
851
852 if( term->id == fish->pos[1]*world.w + fish->pos[0] )
853 {
854 term->recv[ term->recv_count ++ ] = fish->payload;
855 break;
856 }
857 }
858
859 fish->alive = 0;
860 continue;
861 }
862
863 if( cell_current->config == k_cell_type_split )
864 {
865 // Flip flop L/R
866 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
867 fish->dir[1] = 0;
868
869 cell_current->state ^= FLAG_FLIP_FLOP;
870 }
871 else if( cell_current->config == k_cell_type_merge )
872 {
873 // Can only move up
874 fish->dir[0] = 0;
875 fish->dir[1] = -1;
876 }
877 else
878 {
879 struct cell *cell_next = pcell( (v2i){ fish->pos[0]+fish->dir[0], fish->pos[1]+fish->dir[1] } );
880 if( !(cell_next->state & (FLAG_CANAL|FLAG_OUTPUT)) )
881 {
882 // Try other directions for valid, so down, left, right..
883 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
884 vg_info( "Trying some other directions...\n" );
885
886 for( int j = 0; j < vg_list_size(dirs); j ++ )
887 {
888 if( (dirs[j][0] == -fish->dir[0]) && (dirs[j][1] == -fish->dir[1]) )
889 continue;
890
891 if( pcell( (v2i){ fish->pos[0]+dirs[j][0], fish->pos[1]+dirs[j][1] } )->state & (FLAG_CANAL|FLAG_OUTPUT) )
892 {
893 fish->dir[0] = dirs[j][0];
894 fish->dir[1] = dirs[j][1];
895 }
896 }
897 }
898 }
899
900 fish->pos[0] += fish->dir[0];
901 fish->pos[1] += fish->dir[1];
902
903 struct cell *cell_entry = pcell( fish->pos );
904
905 if( !(cell_entry->state & (FLAG_INPUT|FLAG_CANAL|FLAG_OUTPUT) ))
906 fish->alive = 0;
907 else
908 {
909 if( fish->dir[0] )
910 {
911 if( cell_entry->config == k_cell_type_split ||
912 cell_entry->config == k_cell_type_ramp_right ||
913 cell_entry->config == k_cell_type_ramp_left )
914 {
915 // Special death (FALL)
916 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
917 v2_divs( fish->physics_v, vg_time_delta, fish->physics_v );
918
919 fish->alive = -2;
920 vg_warn( "Special death (fall)\n" );
921 continue;
922 }
923 }
924
925 if( cell_entry->config == k_cell_type_split )
926 {
927 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
928 cell_entry->state |= FLAG_FLIP_ROTATING;
929 }
930 }
931 }
932
933 // Check for collisions
934 for( int i = 0; i < world.num_fishes; i ++ )
935 {
936 if( world.fishes[i].alive == 1 )
937 {
938 for( int j = i+1; j < world.num_fishes; j ++ )
939 {
940 if( (world.fishes[j].alive == 1) && (world.fishes[i].pos[0] == world.fishes[j].pos[0]) &&
941 (world.fishes[i].pos[1] == world.fishes[j].pos[1]) )
942 {
943 // Shatter death (+0.5s)
944 world.fishes[i].alive = -1;
945 world.fishes[j].alive = -1;
946 world.fishes[i].death_time = 0.5f;
947 world.fishes[j].death_time = 0.5f;
948 }
949 }
950 }
951 }
952
953 // Spawn fishes
954 for( int i = 0; i < arrlen( world.io ); i ++ )
955 {
956 struct cell_terminal *term = &world.io[ i ];
957 int posx = term->id % world.w;
958 int posy = (term->id - posx)/world.w;
959 int is_input = world.data[ term->id ].state & FLAG_INPUT;
960
961 if( is_input )
962 {
963 if( world.sim_frame < arrlen( term->conditions ) )
964 {
965 struct fish *fish = &world.fishes[world.num_fishes++];
966 fish->pos[0] = posx;
967 fish->pos[1] = posy;
968 fish->alive = 1;
969 fish->payload = term->conditions[world.sim_frame];
970
971 int can_spawn = 0;
972
973 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
974 for( int j = 0; j < vg_list_size(dirs); j ++ )
975 if( pcell( (v2i){ posx+dirs[j][0], posy+dirs[j][1] } )->state & FLAG_CANAL )
976 {
977 fish->dir[0] = dirs[j][0];
978 fish->dir[1] = dirs[j][1];
979 can_spawn = 1;
980 break;
981 }
982
983 if( !can_spawn )
984 world.num_fishes--;
985 }
986 }
987 }
988
989 world.sim_frame ++;
990 }
991
992 float scaled_time = 0.0f;
993 scaled_time = (vg_time-world.sim_start)*2.0f;
994 world.frame_lerp = scaled_time - (float)world.sim_frame;
995
996 // Update positions
997 for( int i = 0; i < world.num_fishes; i ++ )
998 {
999 struct fish *fish = &world.fishes[i];
1000
1001 if( fish->alive == 0 )
1002 continue;
1003
1004 if( fish->alive == -1 && (world.frame_lerp > fish->death_time) )
1005 continue; // Todo: particle thing?
1006
1007 if( fish->alive == -2 )
1008 {
1009 v2_muladds( fish->physics_v, (v2f){ 0.0, -9.8f }, vg_time_delta, fish->physics_v );
1010 v2_muladds( fish->physics_co, fish->physics_v, vg_time_delta, fish->physics_co );
1011 }
1012 else
1013 {
1014 struct cell *cell = pcell(fish->pos);
1015 v2f const *curve;
1016
1017 float t = world.frame_lerp;
1018
1019 v2_copy( fish->physics_co, fish->physics_v );
1020
1021 switch( cell->config )
1022 {
1023 case 13:
1024 if( fish->dir[0] == 1 )
1025 curve = curve_12;
1026 else
1027 curve = curve_9;
1028 break;
1029 case 2: curve = curve_2; break;
1030 case 8: curve = curve_8; break;
1031 case 3: curve = curve_3; break;
1032 case 6: curve = curve_6; break;
1033 case 9: curve = curve_9; break;
1034 case 12: curve = curve_12; break;
1035 case 7:
1036 if( t > curve_7_linear_section )
1037 {
1038 t -= curve_7_linear_section;
1039 t *= (1.0f/(1.0f-curve_7_linear_section));
1040
1041 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
1042 }
1043 else curve = NULL;
1044 break;
1045 default: curve = NULL; break;
1046 }
1047
1048 if( curve )
1049 {
1050 float t2 = t * t;
1051 float t3 = t * t * t;
1052
1053 float cA = 3.0f*t2 - 3.0f*t3;
1054 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
1055 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
1056
1057 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
1058 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
1059 fish->physics_co[0] += (float)fish->pos[0];
1060 fish->physics_co[1] += (float)fish->pos[1];
1061 }
1062 else
1063 {
1064 v2f origin;
1065 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
1066 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
1067
1068 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
1069 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
1070 }
1071 }
1072 }
1073 }
1074 }
1075
1076 static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
1077 {
1078 v2i full_start = { 0,0 };
1079 v2i full_end = { world.w, world.h };
1080
1081 if( !start || !end )
1082 {
1083 start = full_start;
1084 end = full_end;
1085 }
1086
1087 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1088
1089 for( int y = start[1]; y < end[1]; y ++ )
1090 {
1091 for( int x = start[0]; x < end[0]; x ++ )
1092 {
1093 struct cell *cell = pcell((v2i){x,y});
1094 int selected = world.selected == y*world.w + x;
1095
1096 int tile_offsets[][2] =
1097 {
1098 {2, 0}, {0, 3}, {0, 2}, {2, 2},
1099 {1, 0}, {2, 3}, {3, 2}, {1, 3},
1100 {3, 1}, {0, 1}, {1, 2}, {2, 1},
1101 {1, 1}, {3, 3}, {2, 1}, {2, 1}
1102 };
1103
1104 int uv[2] = { 3, 0 };
1105
1106 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1107 {
1108 uv[0] = tile_offsets[ cell->config ][0];
1109 uv[1] = tile_offsets[ cell->config ][1];
1110 } else continue;
1111
1112 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
1113 if( selected )
1114 {
1115 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
1116 draw_mesh( 0, 2 );
1117 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1118 }
1119 else
1120 draw_mesh( 0, 2 );
1121 }
1122 }
1123 }
1124
1125 void vg_render(void)
1126 {
1127 glViewport( 0,0, vg_window_x, vg_window_y );
1128
1129 glDisable( GL_DEPTH_TEST );
1130 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
1131 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
1132
1133 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
1134 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
1135
1136 // TILE SET RENDERING
1137 // todo: just slam everything into a mesh...
1138 // when user modifies a tile the neighbours can be easily uploaded to gpu mem
1139 // in ~3 subBuffers
1140 // Currently we're uploading a fair amount of data every frame anyway.
1141 // NOTE: this is for final optimisations ONLY!
1142 // ======================================================================
1143
1144 use_mesh( &world.tile );
1145
1146 // Draw background
1147
1148 if(1){
1149
1150 SHADER_USE( shader_background );
1151 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1152
1153 glActiveTexture( GL_TEXTURE0 );
1154 glBindTexture( GL_TEXTURE_2D, world.background_data );
1155 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
1156
1157 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
1158 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
1159
1160 glActiveTexture( GL_TEXTURE1 );
1161 glBindTexture( GL_TEXTURE_2D, world.random_samples );
1162 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
1163
1164 draw_mesh( 0, 2 );
1165
1166 }
1167
1168
1169 SHADER_USE( shader_tile_main );
1170
1171 m2x2f subtransform;
1172 m2x2_identity( subtransform );
1173 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1174 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1175 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
1176 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
1177
1178 glEnable(GL_BLEND);
1179 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1180 glBlendEquation(GL_FUNC_ADD);
1181
1182 // Bind textures
1183 vg_tex2d_bind( &tex_tile_data, 0 );
1184 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
1185
1186 vg_tex2d_bind( &tex_wood, 1 );
1187 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
1188
1189 render_tiles( NULL, NULL, colour_default, colour_default );
1190
1191
1192
1193 SHADER_USE( shader_ball );
1194 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1195
1196 vg_tex2d_bind( &tex_ball, 0 );
1197 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
1198
1199 // Draw 'fish'
1200 if( world.simulating )
1201 {
1202 for( int i = 0; i < world.num_fishes; i ++ )
1203 {
1204 struct fish *fish = &world.fishes[i];
1205
1206 if( fish->alive == 0 )
1207 continue;
1208
1209 if( fish->alive == -1 && (world.frame_lerp > fish->death_time) )
1210 continue;
1211
1212 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
1213 colour_code_v3( fish->payload, dot_colour );
1214
1215 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
1216 glUniform2fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, fish->physics_co );
1217 draw_mesh( 0, 32 );
1218 }
1219 }
1220
1221 SHADER_USE( shader_tile_main );
1222
1223 // Bind textures
1224 vg_tex2d_bind( &tex_tile_data, 0 );
1225 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
1226
1227 vg_tex2d_bind( &tex_wood, 1 );
1228 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
1229
1230 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
1231 render_tiles( NULL, NULL, colour_default, colour_selected );
1232
1233 // Draw splitters
1234
1235 for( int y = 0; y < world.h; y ++ )
1236 {
1237 for( int x = 0; x < world.w; x ++ )
1238 {
1239 struct cell *cell = pcell((v2i){x,y});
1240
1241 if( cell->config == k_cell_type_split )
1242 {
1243 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
1244
1245 if( cell->state & FLAG_FLIP_ROTATING )
1246 {
1247 if( (world.frame_lerp > curve_7_linear_section) )
1248 {
1249 float const rotation_speed = 0.4f;
1250 if( (world.frame_lerp < 1.0f-rotation_speed) )
1251 {
1252 float t = world.frame_lerp - curve_7_linear_section;
1253 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
1254 t += 1.0f;
1255
1256 rotation *= t;
1257 }
1258 else
1259 rotation *= -1.0f;
1260 }
1261 }
1262
1263 m2x2_create_rotation( subtransform, rotation );
1264
1265 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1266 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y + 0.125f, 0.0f, 0.0f );
1267 draw_mesh( 0, 2 );
1268 }
1269 }
1270 }
1271
1272 // Edit overlay
1273 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) )
1274 {
1275 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
1276 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
1277
1278 world.data[ world.selected ].state ^= FLAG_CANAL;
1279 map_reclassify( new_begin, new_end, 0 );
1280
1281 m2x2_identity( subtransform );
1282 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
1283 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1284 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
1285
1286 render_tiles( new_begin, new_end, colour_default, colour_default );
1287
1288 world.data[ world.selected ].state ^= FLAG_CANAL;
1289 map_reclassify( new_begin, new_end, 0 );
1290 }
1291
1292 //glDisable(GL_BLEND);
1293
1294 glDisable(GL_BLEND);
1295
1296 SHADER_USE( shader_tile_colour );
1297 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1298 use_mesh( &world.circle );
1299
1300 // Draw i/o arrays
1301 for( int i = 0; i < arrlen( world.io ); i ++ )
1302 {
1303 struct cell_terminal *term = &world.io[ i ];
1304 int posx = term->id % world.w;
1305 int posy = (term->id - posx)/world.w;
1306 int is_input = world.data[ term->id ].state & FLAG_INPUT;
1307
1308 int const filled_start = 0;
1309 int const filled_count = 32;
1310 int const empty_start = 32;
1311 int const empty_count = 32*2;
1312
1313 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
1314
1315 for( int j = 0; j < arrlen( term->conditions ); j ++ )
1316 {
1317 float y_offset = is_input? 1.2f: -0.2f;
1318 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)posx + 0.2f + 0.2f * (float)j, (float)posy + y_offset, 0.1f );
1319
1320 if( is_input )
1321 {
1322 colour_code_v3( term->conditions[j], dot_colour );
1323 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1324
1325 // Draw filled if tick not passed, draw empty if empty
1326 if( world.sim_frame > j )
1327 draw_mesh( empty_start, empty_count );
1328 else
1329 draw_mesh( filled_start, filled_count );
1330 }
1331 else
1332 {
1333 if( term->recv_count > j )
1334 {
1335 colour_code_v3( term->recv[j], dot_colour );
1336 v3_muls( dot_colour, 0.8f, dot_colour );
1337 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1338
1339 draw_mesh( filled_start, filled_count );
1340 }
1341
1342 colour_code_v3( term->conditions[j], dot_colour );
1343 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1344
1345 draw_mesh( empty_start, empty_count );
1346 }
1347 }
1348 }
1349
1350 if( world.simulating )
1351 {
1352 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.0f, 0.0f, 0.0f, 1.0f );
1353 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 );
1354 draw_mesh( 0, 32 );
1355 }
1356 }
1357
1358 void vg_ui(void)
1359 {
1360 //ui_test();
1361 }