switch to marbles and create model compiler
[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 m3x3f m_projection;
31 m3x3f m_view;
32 m3x3f m_mdl;
33
34 #define FLAG_INPUT 0x1
35 #define FLAG_OUTPUT 0x2
36 #define FLAG_CANAL 0x4
37 #define FLAG_WALL 0x8
38 #define FLAG_DROP_L 0x10
39 #define FLAG_SPLIT 0x20
40 #define FLAG_MERGER 0x40
41 #define FLAG_DROP_R 0x80
42 #define FLAG_FLIP_FLOP 0x100
43
44 v3f colour_sets[] =
45 { { 0.9f, 0.2f, 0.01f },
46 { 0.2f, 0.9f, 0.14f },
47 { 0.1f, 0.3f, 0.85f } };
48
49 static void colour_code_v3( char cc, v3f target )
50 {
51 if( cc >= 'a' && cc <= 'z' )
52 {
53 int id = cc - 'a';
54
55 if( id < vg_list_size( colour_sets ) )
56 {
57 v3_copy( colour_sets[ id ], target );
58 return;
59 }
60 }
61
62 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
63 }
64
65 struct world
66 {
67 struct cell
68 {
69 u32 state;
70 u8 config;
71 }
72 *data;
73
74 u32 frame;
75
76 u32 sim_frame;
77 float sim_start;
78 int simulating;
79
80 struct cell_terminal
81 {
82 // TODO: Split into input/output structures
83 char *conditions;
84 char recv[12];
85 int recv_count;
86 int id;
87 }
88 *io;
89
90 u32 w, h;
91
92 GLuint tile_vao;
93 GLuint tile_vbo;
94 GLuint circle_vao;
95 GLuint circle_vbo;
96
97 int selected;
98
99 struct fish
100 {
101 v2i pos;
102 v2i dir;
103 int alive;
104 char payload;
105 }
106 fishes[16];
107
108 int num_fishes;
109 } world = {};
110
111 static void map_free(void)
112 {
113 for( int i = 0; i < arrlen( world.io ); i ++ )
114 arrfree( world.io[ i ].conditions );
115
116 arrfree( world.data );
117 arrfree( world.io );
118
119 world.w = 0;
120 world.h = 0;
121 world.data = NULL;
122 world.io = NULL;
123 }
124
125 static int map_load( const char *str )
126 {
127 map_free();
128
129 char const *c = str;
130
131 // Scan for width
132 for(;; world.w ++)
133 {
134 if( str[world.w] == ';' )
135 break;
136 else if( !str[world.w] )
137 {
138 vg_error( "Unexpected EOF when parsing level\n" );
139 return 0;
140 }
141 }
142
143 struct cell *row = arraddnptr( world.data, world.w );
144 int cx = 0;
145 int reg_start = 0, reg_end = 0;
146
147 for(;;)
148 {
149 if( !*c )
150 break;
151
152 if( *c == ';' )
153 {
154 c ++;
155
156 // Parse attribs
157 if( *c != '\n' )
158 {
159 while( *c )
160 {
161 if( reg_start < reg_end )
162 {
163 if( *c >= 'a' && *c <= 'z' )
164 {
165 arrpush( world.io[ reg_start ].conditions, *c );
166 }
167 else
168 {
169 if( *c == ',' || *c == '\n' )
170 {
171 reg_start ++;
172
173 if( *c == '\n' )
174 break;
175 }
176 else
177 {
178 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
179 return 0;
180 }
181 }
182 }
183 else
184 {
185 vg_error( "Too many values to assign (row: %u)\n", world.h );
186 return 0;
187 }
188
189 c ++;
190 }
191 }
192
193 if( reg_start != reg_end )
194 {
195 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
196 return 0;
197 }
198
199 if( cx != world.w )
200 {
201 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
202 return 0;
203 }
204
205 row = arraddnptr( world.data, world.w );
206 cx = 0;
207 world.h ++;
208 reg_end = reg_start = arrlen( world.io );
209 }
210 else
211 {
212 if( cx == world.w )
213 {
214 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
215 return 0;
216 }
217
218 // Tile initialization
219 // row[ cx ] .. etc
220
221 if( *c == '+' || *c == '-' )
222 {
223 struct cell_terminal term = { .id = cx + world.h*world.w };
224 arrpush( world.io, term );
225 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
226 reg_end ++;
227 }
228 else if( *c == '#' )
229 {
230 row[ cx ++ ].state = FLAG_WALL;
231 }
232 else
233 {
234 row[ cx ++ ].state = 0x00;
235 }
236 }
237
238 c ++;
239 }
240
241 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
242 return 1;
243 }
244
245 static struct cell *pcell( v2i pos )
246 {
247 return &world.data[ pos[1]*world.w + pos[0] ];
248 }
249
250 int main( int argc, char *argv[] )
251 {
252 vg_init( argc, argv, "FishLadder" );
253 }
254
255 void vg_register(void)
256 {
257 SHADER_INIT( shader_tile_colour );
258 }
259
260 void vg_start(void)
261 {
262 // Quad mesh
263 {
264 glGenVertexArrays( 1, &world.tile_vao );
265 glGenBuffers( 1, &world.tile_vbo );
266
267 float quad_mesh[] =
268 {
269 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
270 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
271
272 0.48f, 0.48f, 0.5f, 0.52f, 0.52f, 0.52f, // Static dot
273 0.375f, 0.25f, 0.5f, 0.75f, 0.625f, 0.25f, // Downwards pointing arrow
274 0.25f, 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, // Left
275 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, 0.75f, // up
276 0.75f, 0.375f, 0.25f, 0.5f, 0.75f, 0.625f
277 };
278
279 glBindVertexArray( world.tile_vao );
280 glBindBuffer( GL_ARRAY_BUFFER, world.tile_vbo );
281 glBufferData
282 (
283 GL_ARRAY_BUFFER,
284 sizeof( quad_mesh ),
285 quad_mesh,
286 GL_STATIC_DRAW
287 );
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 // Circle mesh
296 {
297 glGenVertexArrays( 1, &world.circle_vao );
298 glGenBuffers( 1, &world.circle_vbo );
299
300 float circle_mesh[32*6*3];
301 int res = vg_list_size( circle_mesh ) / (6*3);
302
303 for( int i = 0; i < res; i ++ )
304 {
305 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
306 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
307
308 circle_mesh[ i*6+0 ] = 0.0f;
309 circle_mesh[ i*6+1 ] = 0.0f;
310
311 v2_copy( v0, circle_mesh + 32*6 + i*12 );
312 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
313 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
314
315 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
316 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
317 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
318
319 v2_copy( v0, circle_mesh + i*6+4 );
320 v2_copy( v1, circle_mesh + i*6+2 );
321 v2_copy( v0, circle_mesh+i*6+4 );
322 v2_copy( v1, circle_mesh+i*6+2 );
323 }
324
325 glBindVertexArray( world.circle_vao );
326 glBindBuffer( GL_ARRAY_BUFFER, world.circle_vbo );
327 glBufferData( GL_ARRAY_BUFFER, sizeof( circle_mesh ), circle_mesh, GL_STATIC_DRAW );
328
329 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
330 glEnableVertexAttribArray( 0 );
331
332 VG_CHECK_GL();
333 }
334
335 map_load
336 (
337 "#############;\n"
338 "###-#####-###;aaa,aa\n"
339 "## ##;\n"
340 "## ##;\n"
341 "## ##;\n"
342 "## ##;\n"
343 "## ##;\n"
344 "## ##;\n"
345 "###+#####+###;aa,aaa\n"
346 "#############;\n"
347 );
348 }
349
350 void vg_free(void)
351 {
352 glDeleteVertexArrays( 1, &world.tile_vao );
353 glDeleteBuffers( 1, &world.tile_vbo );
354
355 glDeleteVertexArrays( 1, &world.circle_vao );
356 glDeleteBuffers( 1, &world.circle_vbo );
357
358 map_free();
359 }
360
361 static int cell_interactive( v2i co )
362 {
363 // Bounds check
364 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
365 return 0;
366
367 // Flags check
368 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
369 return 0;
370
371 // List of 3x3 configurations that we do not allow
372 static u32 invalid_src[][9] =
373 {
374 { 0,1,0,
375 1,1,1,
376 0,1,0
377 },
378 { 0,0,0,
379 0,1,1,
380 0,1,1
381 },
382 { 0,0,0,
383 1,1,0,
384 1,1,0
385 },
386 { 0,1,1,
387 0,1,1,
388 0,0,0
389 },
390 { 1,1,0,
391 1,1,0,
392 0,0,0
393 },
394 { 0,1,0,
395 0,1,1,
396 0,1,0
397 },
398 { 0,1,0,
399 1,1,0,
400 0,1,0
401 }
402 };
403
404 // Statically compile invalid configurations into bitmasks
405 static u32 invalid[ vg_list_size(invalid_src) ];
406
407 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
408 {
409 u32 comped = 0x00;
410
411 for( int j = 0; j < 3; j ++ )
412 for( int k = 0; k < 3; k ++ )
413 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
414
415 invalid[i] = comped;
416 }
417
418 // Extract 5x5 grid surrounding tile
419 u32 blob = 0x1000;
420 for( int y = co[1]-2; y < co[1]+3; y ++ )
421 for( int x = co[0]-2; x < co[0]+3; x ++ )
422 {
423 struct cell *cell = pcell((v2i){x,y});
424
425 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
426 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
427 }
428
429 // Run filter over center 3x3 grid to check for invalid configurations
430 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
431 for( int i = 0; i < vg_list_size(kernel); i ++ )
432 {
433 if( blob & (0x1 << (6+kernel[i])) )
434 {
435 u32 window = blob >> kernel[i];
436
437 for( int j = 0; j < vg_list_size(invalid); j ++ )
438 if((window & invalid[j]) == invalid[j])
439 return 0;
440 }
441 }
442
443 return 1;
444 }
445
446 void vg_update(void)
447 {
448 float ratio = (float)vg_window_y / (float)vg_window_x;
449 float const size = 9.5f;
450
451 v3f origin;
452 origin[0] = -0.5f * world.w;
453 origin[1] = -0.5f * world.h;
454 origin[2] = 0.0f;
455
456 m3x3_projection( m_projection, -size, size, -size*ratio, size*ratio );
457 m3x3_identity( m_view );
458 m3x3_translate( m_view, origin );
459 m3x3_mul( m_projection, m_view, vg_pv );
460 vg_projection_update();
461
462 // Input stuff
463
464 v2f tile_pos;
465 v2_copy( vg_mouse_ws, tile_pos );
466
467 int tile_x = floorf( tile_pos[0] );
468 int tile_y = floorf( tile_pos[1] );
469
470 // Tilemap editing
471 if( !world.simulating )
472 {
473 if( cell_interactive( (v2i){ tile_x, tile_y } ))
474 {
475 world.selected = tile_y * world.w + tile_x;
476
477 if( vg_get_button_down("primary") )
478 {
479 world.data[ world.selected ].state ^= FLAG_CANAL;
480 }
481 }
482 else
483 world.selected = -1;
484 }
485
486 // Simulation stop/start
487 if( vg_get_button_down("go") )
488 {
489 if( world.simulating )
490 {
491 world.simulating = 0;
492 world.num_fishes = 0;
493 world.sim_frame = 0;
494
495 for( int i = 0; i < arrlen( world.io ); i ++ )
496 world.io[i].recv_count = 0;
497
498 vg_info( "Stopping simulation!\n" );
499 }
500 else
501 {
502 vg_success( "Starting simulation!\n" );
503
504 world.simulating = 1;
505 world.num_fishes = 0;
506 world.sim_frame = 0;
507 world.sim_start = vg_time;
508
509 for( int i = 0; i < world.w*world.h; i ++ )
510 {
511 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
512 }
513
514 for( int i = 0; i < arrlen( world.io ); i ++ )
515 world.io[i].recv_count = 0;
516 }
517 }
518
519 // Simulation stuff
520 // ========================================================
521
522 // Reclassify world. TODO: Move into own function
523 for( int y = 2; y < world.h-2; y ++ )
524 {
525 for( int x = 2; x < world.w-2; x ++ )
526 {
527 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
528
529 u8 config = 0x00;
530
531 if( pcell((v2i){x,y})->state & FLAG_CANAL )
532 {
533 for( int i = 0; i < vg_list_size( dirs ); i ++ )
534 {
535 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
536 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
537 config |= 0x1 << i;
538 }
539 } else config = 0xF;
540
541 pcell((v2i){x,y})->config = config;
542 pcell((v2i){x,y})->state &= ~(FLAG_DROP_L|FLAG_DROP_R|FLAG_SPLIT|FLAG_MERGER);
543 }
544 }
545
546 for( int y = 2; y < world.h-2; y ++ )
547 for( int x = 2; x < world.w-2; x ++ )
548 {
549 // R,D,L,- 1110 (splitter, 1 drop created)
550
551 // R,-,L,U - 1011 (merger, 2 drop created)
552
553 u8 config = pcell((v2i){x,y})->config;
554
555 if( config == 0x7 ) // splitter
556 {
557 world.data[y*world.w+x ].state |= (FLAG_SPLIT | FLAG_DROP_L | FLAG_DROP_R);
558 }
559 else if( config == 0xD )
560 {
561 world.data[y*world.w+x-1].state |= FLAG_DROP_R;
562 world.data[y*world.w+x+1].state |= FLAG_DROP_L;
563 world.data[y*world.w+x].state |= FLAG_MERGER;
564 }
565 }
566
567 // Fish ticks
568 if( world.simulating )
569 {
570 while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) )
571 {
572 vg_info( "frame: %u\n", world.sim_frame );
573
574 for( int i = 0; i < arrlen( world.io ); i ++ )
575 {
576 struct cell_terminal *term = &world.io[ i ];
577 int posx = term->id % world.w;
578 int posy = (term->id - posx)/world.w;
579 int is_input = world.data[ term->id ].state & FLAG_INPUT;
580
581 if( is_input )
582 {
583 if( world.sim_frame < arrlen( term->conditions ) )
584 {
585 struct fish *fish = &world.fishes[world.num_fishes++];
586 fish->pos[0] = posx;
587 fish->pos[1] = posy;
588 fish->alive = 1;
589 fish->payload = term->conditions[world.sim_frame];
590
591 int can_spawn = 0;
592
593 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
594 for( int j = 0; j < vg_list_size(dirs); j ++ )
595 {
596 v2i target;
597 if( pcell( (v2i){ posx+dirs[j][0], posy+dirs[j][1] } )->state & FLAG_CANAL )
598 {
599 fish->dir[0] = dirs[j][0];
600 fish->dir[1] = dirs[j][1];
601 can_spawn = 1;
602 break;
603 }
604 }
605
606 if( !can_spawn )
607 world.num_fishes--;
608 }
609 }
610 }
611
612 for( int i = 0; i < world.num_fishes; i ++ )
613 {
614 struct fish *fish = &world.fishes[i];
615 struct cell *cell_current = pcell( fish->pos );
616
617 if( !fish->alive )
618 continue;
619
620 // Apply to output
621 if( cell_current->state & FLAG_OUTPUT )
622 {
623 for( int j = 0; j < arrlen( world.io ); j ++ )
624 {
625 struct cell_terminal *term = &world.io[j];
626
627 if( term->id == fish->pos[1]*world.w + fish->pos[0] )
628 {
629 term->recv[ term->recv_count ++ ] = fish->payload;
630 break;
631 }
632 }
633
634 fish->alive = 0;
635 continue;
636 }
637
638 if( !(cell_current->state & (FLAG_INPUT|FLAG_CANAL)) )
639 {
640 fish->alive = 0;
641 }
642 else
643 {
644 if( cell_current->state & FLAG_SPLIT )
645 {
646 // Flip flop L/R
647 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
648 fish->dir[1] = 0;
649
650 cell_current->state ^= FLAG_FLIP_FLOP;
651 }
652 else if( cell_current->state & FLAG_MERGER )
653 {
654 // Can only move up
655 fish->dir[0] = 0;
656 fish->dir[1] = -1;
657 }
658 else
659 {
660 struct cell *cell_next = pcell( (v2i){ fish->pos[0]+fish->dir[0], fish->pos[1]+fish->dir[1] } );
661 if( !(cell_next->state & FLAG_CANAL) )
662 {
663 // Try other directions for valid, so down, left, right..
664 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
665 vg_info( "Trying some other directions...\n" );
666
667 for( int j = 0; j < vg_list_size(dirs); j ++ )
668 {
669 if( (dirs[j][0] == -fish->dir[0]) && (dirs[j][1] == -fish->dir[1]) )
670 continue;
671
672 if( pcell( (v2i){ fish->pos[0]+dirs[j][0], fish->pos[1]+dirs[j][1] } )->state & FLAG_CANAL )
673 {
674 fish->dir[0] = dirs[j][0];
675 fish->dir[1] = dirs[j][1];
676 }
677 }
678 }
679 }
680
681 fish->pos[0] += fish->dir[0];
682 fish->pos[1] += fish->dir[1];
683 }
684 }
685
686 world.sim_frame ++;
687 }
688 }
689 }
690
691 void vg_render(void)
692 {
693 glViewport( 0,0, vg_window_x, vg_window_y );
694
695 glDisable( GL_DEPTH_TEST );
696 glClearColor( 0.8f, 0.8f, 0.8f, 1.0f );
697 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
698
699 glBindVertexArray( world.tile_vao );
700 SHADER_USE( shader_tile_colour );
701 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
702
703 // Shadow layer
704 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.5f, 0.5f, 0.5f, 1.0f );
705 for( int y = 0; y < world.h; y ++ )
706 for( int x = 0; x < world.w; x ++ )
707 {
708 struct cell *cell = pcell((v2i){x,y});
709
710 if( cell->state & FLAG_CANAL )
711 {
712 continue;
713 }
714
715 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x - 0.2f, (float)y - 0.15f, 1.0f );
716 glDrawArrays( GL_TRIANGLES, 0, 6 );
717 }
718
719 for( int y = 0; y < world.h; y ++ )
720 {
721 for( int x = 0; x < world.w; x ++ )
722 {
723 struct cell *cell = pcell((v2i){x,y});
724 int selected = world.selected == y*world.w + x;
725
726 if( cell->state & FLAG_CANAL && !selected )
727 continue;
728
729 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y, 1.0f );
730
731 v4f colour;
732
733 if( cell->state & FLAG_WALL ) { v4_copy( (v4f){ 0.2f, 0.2f, 0.2f, 1.0f }, colour ); }
734 else if( cell->state & FLAG_CANAL ) { v4_copy( (v4f){ 0.6f, 0.6f, 0.6f, 1.0f }, colour ); }
735 else if( cell->state & FLAG_INPUT ) { v4_copy( (v4f){ 0.5f, 0.5f, 0.5f, 1.0f }, colour ); }
736 else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.2f, 0.7f, 0.3f, 1.0f }, colour ); }
737 else v4_copy( (v4f){ 0.9f, 0.9f, 0.9f, 1.0f }, colour );
738
739 //if( cell->water[world.frame&0x1] )
740 // v4_copy( (v4f){ 0.2f, 0.3f, 0.7f * (float)(cell->water[world.frame&0x1]) * (1.0f/16.0f), 1.0f }, colour );
741
742 if( selected )
743 v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour );
744
745 //if( cell->state & (FLAG_SPLIT) )
746 // v4_copy( (v4f){ 0.75f, 0.75f, 0.02f, 1.0f }, colour );
747 //if( cell->state & (FLAG_MERGER) )
748 // v4_copy( (v4f){ 0.75f, 0.02f, 0.75f, 1.0f }, colour );
749
750 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour );
751
752 glDrawArrays( GL_TRIANGLES, 0, 6 );
753
754 if( cell->state & FLAG_CANAL )
755 {
756 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1.0f, 1.0f, 1.0f, 1.0f );
757 glDrawArrays( GL_TRIANGLES, 6, 3 );
758 }
759 }
760 }
761
762 glBindVertexArray( world.circle_vao );
763
764 // Draw i/o arrays
765 for( int i = 0; i < arrlen( world.io ); i ++ )
766 {
767 struct cell_terminal *term = &world.io[ i ];
768 int posx = term->id % world.w;
769 int posy = (term->id - posx)/world.w;
770 int is_input = world.data[ term->id ].state & FLAG_INPUT;
771
772 int const filled_start = 0;
773 int const filled_count = 32*3;
774 int const empty_start = 32*3;
775 int const empty_count = 32*6;
776
777 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
778
779 for( int j = 0; j < arrlen( term->conditions ); j ++ )
780 {
781 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)posx + 0.2f + 0.2f * (float)j, (float)posy + 0.2f, 0.1f );
782
783 if( is_input )
784 {
785 colour_code_v3( term->conditions[j], dot_colour );
786 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
787
788 // Draw filled if tick not passed, draw empty if empty
789 if( world.sim_frame > j )
790 glDrawArrays( GL_TRIANGLES, empty_start, empty_count );
791 else
792 glDrawArrays( GL_TRIANGLES, filled_start, filled_count );
793 }
794 else
795 {
796 if( term->recv_count > j )
797 {
798 colour_code_v3( term->recv[j], dot_colour );
799 v3_muls( dot_colour, 0.8f, dot_colour );
800 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
801
802 glDrawArrays( GL_TRIANGLES, filled_start, filled_count );
803 }
804
805 colour_code_v3( term->conditions[j], dot_colour );
806 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
807
808 glDrawArrays( GL_TRIANGLES, empty_start, empty_count );
809 }
810 }
811 }
812
813 // Draw 'fish'
814 if( world.simulating )
815 {
816 float scaled_time = (vg_time-world.sim_start)*2.0f;
817 float lerp = 1.0f-(scaled_time - (float)world.sim_frame);
818
819 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
820
821 for( int i = 0; i < world.num_fishes; i ++ )
822 {
823 struct fish *fish = &world.fishes[i];
824
825 if( !fish->alive )
826 continue;
827
828 colour_code_v3( fish->payload, dot_colour );
829 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
830
831 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)fish->pos[0] + 0.5f - (float)fish->dir[0]*lerp, (float)fish->pos[1] + 0.25f - (float)fish->dir[1]*lerp, 0.25f );
832 glDrawArrays( GL_TRIANGLES, 0, 32*3 );
833 }
834 }
835 }
836
837 void vg_ui(void){}