line/curve renderer
[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_1[] = {
8 "level0",
9 "level1",
10 "level2",
11 "level3",
12 "level4",
13 "level5",
14 "level6",
15 "level7_combine",
16 "xor_small",
17 "sort"
18 };
19
20 #pragma pack(push,1)
21 struct career_state
22 {
23 u32 version;
24
25 struct career_level
26 {
27 u32 score;
28 u32 time;
29 u32 completed;
30 }
31 levels[ vg_list_size( level_pack_1 ) ];
32 }
33 career = { .version = 1 };
34 #pragma pack(pop)
35
36 static void career_serialize(void)
37 {
38 vg_asset_write( "sav/game.sav", &career, sizeof( struct career_state ) );
39 }
40
41 static void career_load(void)
42 {
43 i64 sz;
44 struct career_state *cr = vg_asset_read_s( "sav/game.sav", &sz );
45
46 memset( (void*)career.levels, 0, vg_list_size(level_pack_1) * sizeof(struct career_level) );
47
48 if( cr )
49 {
50 if( sz > sizeof( struct career_state ) )
51 vg_warn( "This save file is too big! Some levels will be lost\n" );
52
53 if( sz <= offsetof( struct career_state, levels ) )
54 {
55 vg_error( "This save file is too small to have a header\n" );
56 free( cr );
57 return;
58 }
59
60 u32 const size_header = offsetof(struct career_state, levels);
61 u32 const size_levels = sizeof(struct career_state)-size_header;
62 u32 const size_levels_input = sz - size_header;
63
64 memcpy( (void*)career.levels, (void*)cr->levels, size_levels_input );
65
66 if( sz < sizeof( struct career_state ) )
67 {
68 memset( ((void*)career.levels) + size_levels_input, 0, size_levels-size_levels_input );
69 }
70
71 free( cr );
72 vg_success( "Loaded save file... Info:\n" );
73
74 for( int i = 0; i < vg_list_size( career.levels ); i ++ )
75 {
76 struct career_level *lvl = &career.levels[i];
77 vg_info( "Score: %u, Time: %u, Completed: %u\n", lvl->score, lvl->time, lvl->completed );
78 }
79 }
80 else
81 {
82 vg_info( "No save file... Using blank one\n" );
83 }
84 }
85
86 m3x3f m_projection;
87 m3x3f m_view;
88 m3x3f m_mdl;
89
90 #define FLAG_CANAL 0x1
91 #define FLAG_IS_TRIGGER 0x2
92 #define FLAG_RESERVED0 0x4
93 #define FLAG_RESERVED1 0x8
94
95 #define FLAG_INPUT 0x10
96 #define FLAG_OUTPUT 0x20
97 #define FLAG_WALL 0x40
98
99 #define FLAG_FLIP_FLOP 0x100
100 #define FLAG_FLIP_ROTATING 0x200
101 #define FLAG_TARGETED 0x400
102
103 /*
104 0000 0 | 0001 1 | 0010 2 | 0011 3
105 | | | | |
106 X | X= | X | X=
107 | | |
108 0100 4 | 0101 5 | 0110 6 | 0111 7
109 | | | | |
110 =X | =X= | =X | =X=
111 | | |
112 1000 8 | 1001 9 | 1010 10 | 1011 11
113 | | | | |
114 X | X= | X | X=
115 | | | | | | |
116 1100 12 | 1101 13 | 1110 14 | 1111 15
117 | | | | |
118 =X | =X= | =X | =X=
119 | | | | | | |
120 */
121
122 enum cell_type
123 {
124 k_cell_type_ramp_right = 3,
125 k_cell_type_ramp_left = 6,
126 k_cell_type_split = 7,
127 k_cell_type_merge = 13
128 };
129
130 v3f colour_sets[] =
131 { { 0.9f, 0.6f, 0.20f },
132 { 0.2f, 0.9f, 0.14f },
133 { 0.4f, 0.8f, 1.00f } };
134
135 static void colour_code_v3( char cc, v3f target )
136 {
137 if( cc >= 'a' && cc <= 'z' )
138 {
139 int id = cc - 'a';
140
141 if( id < vg_list_size( colour_sets ) )
142 {
143 v3_copy( colour_sets[ id ], target );
144 return;
145 }
146 }
147
148 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
149 }
150
151 struct mesh
152 {
153 GLuint vao, vbo;
154 u32 elements;
155 };
156
157 static void init_mesh( struct mesh *m, float const *tris, u32 length )
158 {
159 m->elements = length/3;
160 glGenVertexArrays( 1, &m->vao );
161 glGenBuffers( 1, &m->vbo );
162
163 glBindVertexArray( m->vao );
164 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
165 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
166
167 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
168 glEnableVertexAttribArray( 0 );
169
170 VG_CHECK_GL();
171 }
172
173 static void free_mesh( struct mesh *m )
174 {
175 glDeleteVertexArrays( 1, &m->vao );
176 glDeleteBuffers( 1, &m->vbo );
177 }
178
179 static void draw_mesh( int const start, int const count )
180 {
181 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
182 }
183
184 static void use_mesh( struct mesh *m )
185 {
186 glBindVertexArray( m->vao );
187 }
188
189 struct world
190 {
191 #pragma pack(push,1)
192 struct cell
193 {
194 u16 state;
195 u16 links[2];
196 u8 config;
197 u8 pad0;
198 }
199 *data;
200 #pragma pack(pop)
201
202 int frame;
203
204 int initialzed;
205
206 int sim_frame;
207 float sim_start;
208 int simulating;
209 int sim_run, max_runs;
210
211 float frame_lerp;
212
213 struct cell_terminal
214 {
215 //char *conditions;
216 //char recv[12];
217
218 struct terminal_run
219 {
220 char conditions[8];
221 char recieved[8];
222
223 int condition_count, recv_count;
224 }
225 runs[8];
226
227 int run_count;
228
229 int id;
230 }
231 *io;
232
233 int w, h;
234
235 struct mesh tile, circle, numbers;
236 struct mesh_wire
237 {
238 GLuint vao, vbo, ebo;
239 u32 em;
240 }
241 wire;
242
243 GLuint background_data;
244 GLuint random_samples;
245
246 int selected, tile_x, tile_y;
247 v2f tile_pos;
248
249 struct fish
250 {
251 v2i pos;
252 v2i dir;
253 int alive;
254 char payload;
255 float death_time;
256 v2f physics_v;
257 v2f physics_co;
258 }
259 fishes[16];
260
261 int num_fishes;
262
263 char map_name[128];
264 struct career_level *ptr_career_level;
265
266 u32 score;
267 u32 completed;
268 u32 time;
269
270 } world = {};
271
272 static void map_free(void)
273 {
274 arrfree( world.data );
275 arrfree( world.io );
276
277 world.w = 0;
278 world.h = 0;
279 world.data = NULL;
280 world.io = NULL;
281 world.score = 0;
282 world.time = 0;
283 world.completed = 0;
284 world.max_runs = 0;
285 world.initialzed = 0;
286 }
287
288 static void io_reset(void)
289 {
290 for( int i = 0; i < arrlen( world.io ); i ++ )
291 {
292 struct cell_terminal *term = &world.io[i];
293
294 for( int j = 0; j < term->run_count; j ++ )
295 term->runs[j].recv_count = 0;
296 }
297 }
298
299 static void map_reclassify( v2i start, v2i end, int update_texbuffer );
300 static int map_load( const char *str, const char *name )
301 {
302 //TODO: It may be worthwhile, at this point, to switch to binary encoding for save data
303
304 map_free();
305
306 char const *c = str;
307
308 // Scan for width
309 for(;; world.w ++)
310 {
311 if( str[world.w] == ';' )
312 break;
313 else if( !str[world.w] )
314 {
315 vg_error( "Unexpected EOF when parsing level\n" );
316 return 0;
317 }
318 }
319
320 struct cell *row = arraddnptr( world.data, world.w );
321 int cx = 0;
322 int reg_start = 0, reg_end = 0;
323
324 u32 *links_to_make = NULL;
325 int links_satisfied = 0;
326
327 char link_id_buffer[32];
328 int link_id_n = 0;
329
330 for(;;)
331 {
332 if( !*c )
333 break;
334
335 if( *c == '\r' ) { c ++; continue; } // fuck off windows
336
337 if( *c == ';' )
338 {
339 c ++;
340
341 if( *c == '\r' ) c ++;
342
343 // Parse attribs
344 if( *c != '\n' )
345 {
346 while( *c )
347 {
348 if( *c == '\r' ) { c ++; continue; }
349
350 if( reg_start < reg_end )
351 {
352 struct cell_terminal *terminal = &world.io[ reg_start ];
353 struct terminal_run *run = &terminal->runs[ terminal->run_count-1 ];
354
355 if( *c >= 'a' && *c <= 'z' )
356 {
357 run->conditions[ run->condition_count ++ ] = *c;
358 }
359 else
360 {
361 if( *c == ',' || *c == '\n' )
362 {
363 reg_start ++;
364
365 if( *c == '\n' )
366 break;
367 }
368 else if( *c == ':' )
369 {
370 terminal->runs[ terminal->run_count ].condition_count = 0;
371 terminal->run_count ++;
372 world.max_runs = vg_max( world.max_runs, terminal->run_count );
373 }
374 else
375 {
376 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
377 goto IL_REG_ERROR;
378 }
379 }
380 }
381 else
382 {
383 if( links_satisfied < arrlen( links_to_make ) )
384 {
385 struct cell *target = &world.data[ links_to_make[ links_satisfied ] ];
386
387 if( (((u32)*c >= (u32)'0') && ((u32)*c <= (u32)'9')) || *c == '-' )
388 {
389 if( link_id_n >= vg_list_size( link_id_buffer )-1 )
390 {
391 vg_error( "Number was way too long to be parsed (row: %u)\n", world.h );
392 goto IL_REG_ERROR;
393 }
394
395 link_id_buffer[ link_id_n ++ ] = *c;
396 }
397 else if( *c == ',' || *c == '\n' )
398 {
399 link_id_buffer[ link_id_n ] = 0x00;
400 int value = atoi( link_id_buffer );
401
402 target->links[value >= 0? 1:0] = abs(value);
403 links_satisfied ++;
404 link_id_n = 0;
405
406 if( *c == '\n' )
407 break;
408 }
409 else
410 {
411 vg_error( "Invalid character '%c' (row: %u)\n", *c, world.h );
412 goto IL_REG_ERROR;
413 }
414 }
415 else
416 {
417 vg_error( "Too many values to assign (row: %u)\n", world.h );
418 goto IL_REG_ERROR;
419 }
420 }
421
422 c ++;
423 }
424 }
425
426 // Registry length-error checks
427 if( reg_start != reg_end )
428 {
429 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
430 goto IL_REG_ERROR;
431 }
432
433 if( links_satisfied != arrlen( links_to_make ) )
434 {
435 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n", world.h, links_satisfied, arrlen( links_to_make ) );
436 goto IL_REG_ERROR;
437 }
438
439 if( cx != world.w )
440 {
441 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
442 goto IL_REG_ERROR;
443 }
444
445 row = arraddnptr( world.data, world.w );
446 cx = 0;
447 world.h ++;
448 reg_end = reg_start = arrlen( world.io );
449
450 arrsetlen( links_to_make, 0 );
451 links_satisfied = 0;
452 }
453 else
454 {
455 if( cx == world.w )
456 {
457 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
458 goto IL_REG_ERROR;
459 }
460
461 // Tile initialization
462 // row[ cx ] .. etc
463 struct cell *cell = &row[ cx ];
464
465 if( *c == '+' || *c == '-' )
466 {
467 struct cell_terminal *term = arraddnptr( world.io, 1 );
468 term->id = cx + world.h*world.w;
469 term->run_count = 1;
470 term->runs[0].condition_count = 0;
471
472 cell->state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
473 reg_end ++;
474 }
475 else if( *c == '#' ) cell->state = FLAG_WALL;
476 else if( ((u32)*c >= (u32)'A') && ((u32)*c <= (u32)'A'+0xf) )
477 {
478 // Canal flag bits (4bit/16 value):
479 // 0: Canal present
480 // 1: Is trigger
481 // 2: Reserved
482 // 3: Reserved
483
484 cell->state = ((u32)*c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
485
486 if( cell->state & FLAG_IS_TRIGGER )
487 arrpush( links_to_make, cx + world.h*world.w );
488
489 cell->links[0] = 0;
490 cell->links[1] = 0;
491 world.score ++;
492 }
493 else cell->state = 0x00;
494
495 cx ++;
496 }
497
498 c ++;
499 }
500
501 // Update data texture to fill out the background
502 {
503 u8 info_buffer[64*64*4];
504 for( int i = 0; i < 64*64; i ++ )
505 {
506 u8 *px = &info_buffer[i*4];
507 px[0] = 255;
508 px[1] = 0;
509 px[2] = 0;
510 px[3] = 0;
511 }
512
513 glBindTexture( GL_TEXTURE_2D, world.background_data );
514 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
515 }
516
517 arrfree( links_to_make );
518
519 map_reclassify( NULL, NULL, 1 );
520
521 // Validate links
522 for( int i = 0; i < world.h*world.w; i ++ )
523 {
524 struct cell *src = &world.data[i];
525 if( src->state & FLAG_IS_TRIGGER )
526 {
527 int link_id = src->links[0]?0:1;
528 if( src->links[link_id] <= world.h*world.w )
529 {
530 struct cell *target = &world.data[ src->links[link_id] ];
531 if( (target->state & FLAG_CANAL) && (target->config == k_cell_type_split) )
532 {
533 if( target->links[ link_id ] )
534 {
535 vg_error( "Link target was already targeted\n" );
536 goto IL_REG_ERROR;
537 }
538 else
539 {
540 // Valid link
541 target->links[ link_id ] = i;
542 target->state |= FLAG_TARGETED;
543 }
544 }
545 else
546 {
547 vg_error( "Link target was invalid\n" );
548 goto IL_REG_ERROR;
549 }
550 }
551 else
552 {
553 vg_error( "Link target out of bounds\n" );
554 goto IL_REG_ERROR;
555 }
556 }
557 }
558
559 vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
560
561 io_reset();
562
563 strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
564 world.initialzed = 1;
565 return 1;
566
567 IL_REG_ERROR:
568 arrfree( links_to_make );
569 map_free();
570 return 0;
571 }
572
573 static struct cell *pcell( v2i pos )
574 {
575 return &world.data[ pos[1]*world.w + pos[0] ];
576 }
577
578 static void map_serialize( FILE *stream )
579 {
580 for( int y = 0; y < world.h; y ++ )
581 {
582 for( int x = 0; x < world.w; x ++ )
583 {
584 struct cell *cell = pcell( (v2i){ x, y } );
585
586 if( cell->state & FLAG_WALL ) fputc( '#', stream );
587 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
588 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
589 else if( cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1) )
590 {
591 fputc( (cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)) + (u32)'A', stream );
592 }
593 else fputc( ' ', stream );
594 }
595
596 fputc( ';', stream );
597
598 int terminal_write_count = 0;
599
600 for( int x = 0; x < world.w; x ++ )
601 {
602 for( int i = 0; i < arrlen( world.io ); i ++ )
603 {
604 struct cell_terminal *term = &world.io[i];
605 if( term->id == y*world.w+x )
606 {
607 if( terminal_write_count )
608 fputc( ',', stream );
609 terminal_write_count ++;
610
611 for( int j = 0; j < term->run_count; j ++ )
612 {
613 struct terminal_run *run = &term->runs[j];
614
615 for( int k = 0; k < run->condition_count; k ++ )
616 fputc( run->conditions[k], stream );
617
618 if( j < term->run_count-1 )
619 fputc( ':', stream );
620 }
621 }
622 }
623 }
624
625 for( int x = 0; x < world.w; x ++ )
626 {
627 struct cell *cell = pcell( (v2i){ x,y } );
628 if( cell->state & FLAG_IS_TRIGGER )
629 {
630 if( terminal_write_count )
631 fputc( ',', stream );
632 terminal_write_count ++;
633
634 fprintf( stream, "%d", cell->links[0]? -cell->links[0]: cell->links[1] );
635 }
636 }
637
638 fputc( '\n', stream );
639 }
640 }
641
642 int main( int argc, char *argv[] )
643 {
644 vg_init( argc, argv, "Fish (Marbles Computer) Ladder Simulator 2022 | N,M: change level | SPACE: Test | LeftClick: Toggle tile" );
645 }
646
647 static int console_save_map( int argc, char const *argv[] )
648 {
649 if( !world.initialzed )
650 {
651 vg_error( "Tried to save uninitialized map!\n" );
652 return 0;
653 }
654
655 char map_path[ 256 ];
656
657 strcpy( map_path, "sav/" );
658 strcat( map_path, world.map_name );
659 strcat( map_path, ".map" );
660
661 FILE *test_writer = fopen( map_path, "wb" );
662 if( test_writer )
663 {
664 vg_info( "Saving map to '%s'\n", map_path );
665 map_serialize( test_writer );
666
667 fclose( test_writer );
668 return 1;
669 }
670 else
671 {
672 vg_error( "Unable to open stream for writing\n" );
673 return 0;
674 }
675 }
676
677 static int console_load_map( int argc, char const *argv[] )
678 {
679 char map_path[ 256 ];
680
681 if( argc >= 1 )
682 {
683 // try from saves
684 strcpy( map_path, "sav/" );
685 strcat( map_path, argv[0] );
686 strcat( map_path, ".map" );
687
688 char *text_source = vg_textasset_read( map_path );
689
690 if( !text_source )
691 {
692 strcpy( map_path, "maps/" );
693 strcat( map_path, argv[0] );
694 strcat( map_path, ".map" );
695
696 text_source = vg_textasset_read( map_path );
697 }
698
699 if( text_source )
700 {
701 vg_info( "Loading map: '%s'\n", map_path );
702 world.ptr_career_level = NULL;
703
704 if( !map_load( text_source, argv[0] ) )
705 {
706 free( text_source );
707 return 0;
708 }
709
710 free( text_source );
711
712 for( int i = 0; i < vg_list_size( level_pack_1 ); i ++ )
713 {
714 if( !strcmp( level_pack_1[i], argv[0] ) )
715 {
716 world.ptr_career_level = career.levels + i;
717 break;
718 }
719 }
720
721 return 1;
722 }
723 else
724 {
725 vg_error( "Missing maps '%s'\n", argv[0] );
726 return 0;
727 }
728 }
729 else
730 {
731 vg_error( "Missing argument <map_path>\n" );
732 return 0;
733 }
734 }
735
736 static void simulation_stop(void)
737 {
738 world.simulating = 0;
739 world.num_fishes = 0;
740 world.sim_frame = 0;
741
742 io_reset();
743
744 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
745
746 vg_info( "Stopping simulation!\n" );
747 }
748
749 static int console_changelevel( int argc, char const *argv[] )
750 {
751 if( argc >= 1 )
752 {
753 // Save current level
754 console_save_map( 0, NULL );
755 if( console_load_map( argc, argv ) )
756 {
757 simulation_stop();
758 return 1;
759 }
760 }
761 else
762 {
763 vg_error( "Missing argument <map_path>\n" );
764 }
765
766 return 0;
767 }
768
769 void vg_start(void)
770 {
771 vg_function_push( (struct vg_cmd){
772 .name = "_map_write",
773 .function = console_save_map
774 });
775
776 vg_function_push( (struct vg_cmd){
777 .name = "_map_load",
778 .function = console_load_map
779 });
780
781 vg_function_push( (struct vg_cmd){
782 .name = "map",
783 .function = console_changelevel
784 });
785
786 // Quad mesh
787 {
788 float quad_mesh[] =
789 {
790 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
791 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
792
793 0.0f, 0.0f, 0.0f, 1.0f, 4.0f, 1.0f,
794 0.0f, 0.0f, 4.0f, 1.0f, 4.0f, 0.0f
795 };
796
797 init_mesh( &world.tile, quad_mesh, vg_list_size(quad_mesh) );
798 }
799
800 // Circle mesh
801 {
802 float circle_mesh[32*6*3];
803 int res = vg_list_size( circle_mesh ) / (6*3);
804
805 for( int i = 0; i < res; i ++ )
806 {
807 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
808 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
809
810 circle_mesh[ i*6+0 ] = 0.0f;
811 circle_mesh[ i*6+1 ] = 0.0f;
812
813 v2_copy( v0, circle_mesh + 32*6 + i*12 );
814 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
815 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
816
817 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
818 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
819 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
820
821 v2_copy( v0, circle_mesh + i*6+4 );
822 v2_copy( v1, circle_mesh + i*6+2 );
823 v2_copy( v0, circle_mesh+i*6+4 );
824 v2_copy( v1, circle_mesh+i*6+2 );
825 }
826
827 init_mesh( &world.circle, circle_mesh, vg_list_size( circle_mesh ) );
828 }
829
830 // Numbers mesh
831 {
832 init_mesh( &world.numbers,
833 MESH_NUMBERS_BUFFER,
834 vg_list_size( MESH_NUMBERS_BUFFER )
835 );
836
837 for( int i = 0; i < 10; i ++ )
838 {
839 vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] );
840 }
841 }
842
843 // Create wire mesh
844 {
845 int const num_segments = 64;
846
847 struct mesh_wire *mw = &world.wire;
848
849 v2f wire_points[ num_segments * 2 ];
850 u16 wire_indices[ 6*(num_segments-1) ];
851
852 for( int i = 0; i < num_segments; i ++ )
853 {
854 float l = (float)i / (float)(num_segments-1);
855
856 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
857 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
858
859 if( i < num_segments-1 )
860 {
861 wire_indices[ i*6+0 ] = i*2 + 0;
862 wire_indices[ i*6+1 ] = i*2 + 1;
863 wire_indices[ i*6+2 ] = i*2 + 3;
864 wire_indices[ i*6+3 ] = i*2 + 0;
865 wire_indices[ i*6+4 ] = i*2 + 3;
866 wire_indices[ i*6+5 ] = i*2 + 2;
867 }
868 }
869
870 glGenVertexArrays( 1, &mw->vao );
871 glGenBuffers( 1, &mw->vbo );
872 glGenBuffers( 1, &mw->ebo );
873 glBindVertexArray( mw->vao );
874
875 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
876
877 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
878 glBindVertexArray( mw->vao );
879
880 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
881 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
882
883 // XY
884 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
885 glEnableVertexAttribArray( 0 );
886
887 VG_CHECK_GL();
888
889 mw->em = vg_list_size( wire_indices );
890 }
891
892 // Create info data texture
893 {
894 glGenTextures( 1, &world.background_data );
895 glBindTexture( GL_TEXTURE_2D, world.background_data );
896 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
897 vg_tex2d_nearest();
898 }
899
900 // Create random smaples texture
901 {
902 u8 *data = malloc(512*512*2);
903 for( int i = 0; i < 512*512*2; i ++ )
904 data[ i ] = rand()/(RAND_MAX/255);
905
906 glGenTextures( 1, &world.random_samples );
907 glBindTexture( GL_TEXTURE_2D, world.random_samples );
908 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
909 vg_tex2d_linear();
910 vg_tex2d_repeat();
911
912 free( data );
913 }
914
915 resource_load_main();
916
917 // Restore gamestate
918 career_load();
919 console_load_map( 1, level_pack_1 );
920 }
921
922 void vg_free(void)
923 {
924 console_save_map( 0, NULL );
925 career_serialize();
926
927 resource_free_main();
928
929 glDeleteTextures( 1, &world.background_data );
930 glDeleteTextures( 1, &world.random_samples );
931
932 glDeleteVertexArrays( 1, &world.wire.vao );
933 glDeleteBuffers( 1, &world.wire.vbo );
934 glDeleteBuffers( 1, &world.wire.ebo );
935
936 free_mesh( &world.tile );
937 free_mesh( &world.circle );
938 free_mesh( &world.numbers );
939
940 map_free();
941 }
942
943 static int cell_interactive( v2i co )
944 {
945 // Bounds check
946 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
947 return 0;
948
949 // Flags check
950 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
951 return 0;
952
953 // List of 3x3 configurations that we do not allow
954 static u32 invalid_src[][9] =
955 {
956 { 0,1,0,
957 1,1,1,
958 0,1,0
959 },
960 { 0,0,0,
961 0,1,1,
962 0,1,1
963 },
964 { 0,0,0,
965 1,1,0,
966 1,1,0
967 },
968 { 0,1,1,
969 0,1,1,
970 0,0,0
971 },
972 { 1,1,0,
973 1,1,0,
974 0,0,0
975 },
976 { 0,1,0,
977 0,1,1,
978 0,1,0
979 },
980 { 0,1,0,
981 1,1,0,
982 0,1,0
983 }
984 };
985
986 // Statically compile invalid configurations into bitmasks
987 static u32 invalid[ vg_list_size(invalid_src) ];
988
989 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
990 {
991 u32 comped = 0x00;
992
993 for( int j = 0; j < 3; j ++ )
994 for( int k = 0; k < 3; k ++ )
995 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
996
997 invalid[i] = comped;
998 }
999
1000 // Extract 5x5 grid surrounding tile
1001 u32 blob = 0x1000;
1002 for( int y = co[1]-2; y < co[1]+3; y ++ )
1003 for( int x = co[0]-2; x < co[0]+3; x ++ )
1004 {
1005 struct cell *cell = pcell((v2i){x,y});
1006
1007 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
1008 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1009 }
1010
1011 // Run filter over center 3x3 grid to check for invalid configurations
1012 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1013 for( int i = 0; i < vg_list_size(kernel); i ++ )
1014 {
1015 if( blob & (0x1 << (6+kernel[i])) )
1016 {
1017 u32 window = blob >> kernel[i];
1018
1019 for( int j = 0; j < vg_list_size(invalid); j ++ )
1020 if((window & invalid[j]) == invalid[j])
1021 return 0;
1022 }
1023 }
1024
1025 return 1;
1026 }
1027
1028 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
1029 {
1030 v2i full_start = { 1,1 };
1031 v2i full_end = { world.w-1, world.h-1 };
1032
1033 if( !start || !end )
1034 {
1035 start = full_start;
1036 end = full_end;
1037 }
1038
1039 // Texture data
1040 u8 info_buffer[64*64*4];
1041 u32 pixel_id = 0;
1042
1043 int px0 = vg_max( start[0], full_start[0] ),
1044 px1 = vg_min( end[0], full_end[0] ),
1045 py0 = vg_max( start[1], full_start[1] ),
1046 py1 = vg_min( end[1], full_end[1] );
1047
1048 for( int y = py0; y < py1; y ++ )
1049 {
1050 for( int x = px0; x < px1; x ++ )
1051 {
1052 struct cell *cell = pcell((v2i){x,y});
1053
1054 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
1055
1056 u8 height = 0;
1057 u8 config = 0x00;
1058
1059 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1060 {
1061 for( int i = 0; i < vg_list_size( dirs ); i ++ )
1062 {
1063 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
1064 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1065 config |= 0x1 << i;
1066 }
1067
1068 height = 128;
1069 }
1070 else
1071 {
1072 if( cell->state & FLAG_WALL )
1073 height = 255;
1074
1075 config = 0xF;
1076 }
1077
1078 pcell((v2i){x,y})->config = config;
1079
1080 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
1081 info_px[0] = height;
1082 info_px[1] = cell->state & FLAG_WALL? 0: 255;
1083 info_px[2] = 0;
1084 info_px[3] = 0;
1085 }
1086 }
1087
1088 if( update_texbuffer )
1089 {
1090 glBindTexture( GL_TEXTURE_2D, world.background_data );
1091 glTexSubImage2D( GL_TEXTURE_2D, 0, px0 + 16, py0 + 16, px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
1092 }
1093 }
1094
1095
1096 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
1097 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
1098 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
1099 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
1100
1101 v2f const curve_1[] = {{1.0f,0.5f},{0.8f,0.5f},{0.3f,0.5f},{0.2f,0.5f}};
1102 v2f const curve_4[] = {{0.0f,0.5f},{0.3f,0.5f},{0.5f,0.5f},{0.8f,0.5f}};
1103 v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}};
1104 v2f const curve_8[] = {{0.5f,0.8f},{0.5f,0.5f},{0.5f,0.3f},{0.5f,0.0f}};
1105
1106 v2f const curve_7[] = {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
1107 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}};
1108
1109 float const curve_7_linear_section = 0.1562f;
1110
1111 void vg_update(void)
1112 {
1113 // Fit within screen
1114
1115 float r1 = (float)vg_window_y / (float)vg_window_x,
1116 r2 = (float)world.h / (float)world.w,
1117 size;
1118
1119 size = ( r2 < r1? (float)world.w * 0.5f: ((float)world.h * 0.5f) / r1 ) + 2.5f;
1120 m3x3_projection( m_projection, -size, size, -size*r1, size*r1 );
1121
1122 v3f origin;
1123 origin[0] = floorf( -0.5f * world.w );
1124 origin[1] = floorf( -0.5f * world.h );
1125 origin[2] = 0.0f;
1126
1127 m3x3_identity( m_view );
1128 m3x3_translate( m_view, origin );
1129 m3x3_mul( m_projection, m_view, vg_pv );
1130 vg_projection_update();
1131
1132 // Input stuff
1133 v2_copy( vg_mouse_ws, world.tile_pos );
1134
1135 world.tile_x = floorf( world.tile_pos[0] );
1136 world.tile_y = floorf( world.tile_pos[1] );
1137
1138 static u16 id_drag_from = 0;
1139 static v2f drag_from_co;
1140 static v2f drag_to_co;
1141
1142 // Tilemap editing
1143 if( !world.simulating )
1144 {
1145 v2_copy( vg_mouse_ws, drag_to_co );
1146
1147 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1148 {
1149 world.selected = world.tile_y * world.w + world.tile_x;
1150
1151 static u32 modify_state = 0;
1152
1153 struct cell *cell_ptr = &world.data[world.selected];
1154
1155 if( vg_get_button_down("primary") )
1156 {
1157 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1158 }
1159
1160 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1161 {
1162 cell_ptr->state &= ~FLAG_CANAL;
1163 cell_ptr->state |= modify_state;
1164
1165 if( cell_ptr->state & FLAG_CANAL )
1166 {
1167 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1168 world.score ++;
1169 }
1170 else
1171 {
1172 if( cell_ptr->state & (FLAG_IS_TRIGGER|FLAG_TARGETED) )
1173 {
1174 cell_ptr->state &= ~(FLAG_IS_TRIGGER|FLAG_TARGETED);
1175 for( u32 i = 0; i < 2; i ++ )
1176 {
1177 if( cell_ptr->links[i] )
1178 {
1179 struct cell *other_ptr = &world.data[ cell_ptr->links[i] ];
1180 other_ptr->links[ i ] = 0;
1181 other_ptr->state &= ~FLAG_IS_TRIGGER;
1182
1183 if( other_ptr->links[ i ^ 0x1 ] == 0 )
1184 other_ptr->state &= ~(FLAG_TARGETED);
1185 }
1186 }
1187 }
1188
1189 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1190 world.score --;
1191 }
1192
1193 cell_ptr->links[0] = 0;
1194 cell_ptr->links[1] = 0;
1195
1196 map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 },
1197 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1198 }
1199
1200 if( vg_get_button_down("secondary") && !(cell_ptr->config == k_cell_type_split) )
1201 {
1202 id_drag_from = world.selected;
1203 drag_from_co[0] = world.tile_x + 0.5f;
1204 drag_from_co[1] = world.tile_y + 0.5f;
1205 }
1206
1207 if( id_drag_from && (cell_ptr->config == k_cell_type_split) )
1208 {
1209 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1210 drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1211 drag_to_co[1] = (float)world.tile_y + 0.25f;
1212
1213 if( vg_get_button_up("secondary") )
1214 {
1215 struct cell *drag_ptr = &world.data[id_drag_from];
1216 u32 link_id = local_x > 0.5f? 1: 0;
1217
1218 // Cleanup existing connections
1219 if( cell_ptr->links[ link_id ] )
1220 {
1221 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1222
1223 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1224 current_connection->state &= ~FLAG_IS_TRIGGER;
1225 current_connection->links[ link_id ] = 0;
1226 }
1227
1228 if( drag_ptr->links[ link_id ^ 0x1 ] )
1229 {
1230 vg_warn( "Destroying alternate link %u (%hu)\n", link_id ^ 0x1, drag_ptr->links[ link_id ^ 0x1 ] );
1231
1232 struct cell *current_connection = &world.data[ drag_ptr->links[ link_id ^ 0x1 ]];
1233 if( !current_connection->links[ link_id ] )
1234 current_connection->state &= ~FLAG_TARGETED;
1235
1236 current_connection->links[ link_id ^ 0x1 ] = 0;
1237 drag_ptr->links[ link_id ^ 0x1 ] = 0;
1238 }
1239
1240 // Create the new connection
1241 vg_success( "Creating connection on link %u (%hu)\n", link_id, id_drag_from );
1242
1243 cell_ptr->links[ link_id ] = id_drag_from;
1244 drag_ptr->links[ link_id ] = world.selected;
1245
1246 cell_ptr->state |= FLAG_TARGETED;
1247 drag_ptr->state |= FLAG_IS_TRIGGER;
1248 id_drag_from = 0;
1249 }
1250 }
1251 }
1252 else
1253 world.selected = -1;
1254
1255 if( vg_get_button("secondary") && id_drag_from )
1256 {
1257 vg_line2( drag_from_co, drag_to_co, 0xff00ff00, 0xffffff00 );
1258 }
1259 else
1260 {
1261 id_drag_from = 0;
1262 }
1263 }
1264 else
1265 {
1266 world.selected = -1;
1267 id_drag_from = 0;
1268 }
1269
1270 // Simulation stop/start
1271 if( vg_get_button_down("go") )
1272 {
1273 if( world.simulating )
1274 {
1275 simulation_stop();
1276 }
1277 else
1278 {
1279 vg_success( "Starting simulation!\n" );
1280
1281 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1282
1283 world.simulating = 1;
1284 world.num_fishes = 0;
1285 world.sim_frame = 0;
1286 world.sim_start = vg_time;
1287 world.sim_run = 0;
1288
1289 for( int i = 0; i < world.w*world.h; i ++ )
1290 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
1291
1292 io_reset();
1293 }
1294 }
1295
1296 // Fish ticks
1297 if( world.simulating )
1298 {
1299 while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) )
1300 {
1301 //vg_info( "frame: %u\n", world.sim_frame );
1302 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 9 );
1303
1304 // Update splitter deltas
1305 for( int i = 0; i < world.h*world.w; i ++ )
1306 {
1307 struct cell *cell = &world.data[i];
1308 if( cell->config == k_cell_type_split )
1309 {
1310 cell->state &= ~FLAG_FLIP_ROTATING;
1311 }
1312 }
1313
1314 int alive_count = 0;
1315
1316 // Update fish positions
1317 for( int i = 0; i < world.num_fishes; i ++ )
1318 {
1319 struct fish *fish = &world.fishes[i];
1320 struct cell *cell_current = pcell( fish->pos );
1321
1322 if( fish->alive == -1 )
1323 fish->alive = 0;
1324
1325 if( fish->alive != 1 )
1326 continue;
1327
1328 // Apply to output
1329 if( cell_current->state & FLAG_OUTPUT )
1330 {
1331 for( int j = 0; j < arrlen( world.io ); j ++ )
1332 {
1333 struct cell_terminal *term = &world.io[j];
1334
1335 if( term->id == fish->pos[1]*world.w + fish->pos[0] )
1336 {
1337 struct terminal_run *run = &term->runs[ world.sim_run ];
1338 if( run->recv_count < vg_list_size( run->recieved ) )
1339 run->recieved[ run->recv_count ++ ] = fish->payload;
1340
1341 break;
1342 }
1343 }
1344
1345 fish->alive = 0;
1346 continue;
1347 }
1348
1349 if( cell_current->config == k_cell_type_split )
1350 {
1351 // Flip flop L/R
1352 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
1353 fish->dir[1] = 0;
1354
1355 if( !(cell_current->state & FLAG_TARGETED) )
1356 cell_current->state ^= FLAG_FLIP_FLOP;
1357 }
1358 else if( cell_current->config == k_cell_type_merge )
1359 {
1360 // Can only move up
1361 fish->dir[0] = 0;
1362 fish->dir[1] = -1;
1363 }
1364 else
1365 {
1366 struct cell *cell_next = pcell( (v2i){ fish->pos[0]+fish->dir[0], fish->pos[1]+fish->dir[1] } );
1367 if( !(cell_next->state & (FLAG_CANAL|FLAG_OUTPUT)) )
1368 {
1369 // Try other directions for valid, so down, left, right..
1370 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
1371 //vg_info( "Trying some other directions...\n" );
1372
1373 for( int j = 0; j < vg_list_size(dirs); j ++ )
1374 {
1375 if( (dirs[j][0] == -fish->dir[0]) && (dirs[j][1] == -fish->dir[1]) )
1376 continue;
1377
1378 if( pcell( (v2i){ fish->pos[0]+dirs[j][0], fish->pos[1]+dirs[j][1] } )->state & (FLAG_CANAL|FLAG_OUTPUT) )
1379 {
1380 fish->dir[0] = dirs[j][0];
1381 fish->dir[1] = dirs[j][1];
1382 }
1383 }
1384 }
1385 }
1386
1387 fish->pos[0] += fish->dir[0];
1388 fish->pos[1] += fish->dir[1];
1389
1390 struct cell *cell_entry = pcell( fish->pos );
1391
1392 if( !(cell_entry->state & (FLAG_INPUT|FLAG_CANAL|FLAG_OUTPUT) ))
1393 fish->alive = 0;
1394 else
1395 {
1396 if( fish->dir[0] )
1397 {
1398 if( cell_entry->config == k_cell_type_split ||
1399 cell_entry->config == k_cell_type_ramp_right ||
1400 cell_entry->config == k_cell_type_ramp_left )
1401 {
1402 // Special death (FALL)
1403 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
1404 v2_divs( fish->physics_v, vg_time_delta, fish->physics_v );
1405
1406 fish->alive = -2;
1407 vg_warn( "Special death (fall)\n" );
1408 continue;
1409 }
1410 }
1411
1412 if( cell_entry->config == k_cell_type_split )
1413 {
1414 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
1415 cell_entry->state |= FLAG_FLIP_ROTATING;
1416 }
1417
1418 if( cell_entry->state & FLAG_IS_TRIGGER )
1419 {
1420 int trigger_id = cell_entry->links[0]?0:1;
1421 int connection_id = cell_entry->links[trigger_id];
1422 int target_px = connection_id % world.w;
1423 int target_py = (connection_id - target_px)/world.w;
1424
1425 vg_line2( (v2f){ fish->pos[0], fish->pos[1] }, (v2f){ target_px, target_py }, 0xffffffff, 0xffffffff );
1426
1427 struct cell *target_peice = &world.data[ cell_entry->links[trigger_id] ];
1428
1429 if( trigger_id )
1430 target_peice->state |= FLAG_FLIP_FLOP;
1431 else
1432 target_peice->state &= ~FLAG_FLIP_FLOP;
1433 }
1434 }
1435
1436 if( fish->alive )
1437 alive_count ++;
1438 }
1439
1440 // Check for collisions
1441 for( int i = 0; i < world.num_fishes; i ++ )
1442 {
1443 if( world.fishes[i].alive == 1 )
1444 {
1445 for( int j = i+1; j < world.num_fishes; j ++ )
1446 {
1447 if( (world.fishes[j].alive == 1) && (world.fishes[i].pos[0] == world.fishes[j].pos[0]) &&
1448 (world.fishes[i].pos[1] == world.fishes[j].pos[1]) )
1449 {
1450 // Shatter death (+0.5s)
1451 world.fishes[i].alive = -1;
1452 world.fishes[j].alive = -1;
1453 world.fishes[i].death_time = 0.5f;
1454 world.fishes[j].death_time = 0.5f;
1455 }
1456 }
1457 }
1458 }
1459
1460 // Spawn fishes
1461 for( int i = 0; i < arrlen( world.io ); i ++ )
1462 {
1463 struct cell_terminal *term = &world.io[ i ];
1464 int posx = term->id % world.w;
1465 int posy = (term->id - posx)/world.w;
1466 int is_input = world.data[ term->id ].state & FLAG_INPUT;
1467
1468 if( is_input )
1469 {
1470 if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
1471 {
1472 struct fish *fish = &world.fishes[world.num_fishes++];
1473 fish->pos[0] = posx;
1474 fish->pos[1] = posy;
1475 fish->alive = 1;
1476 fish->payload = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
1477
1478 int can_spawn = 0;
1479
1480 v2i dirs[] = {{1,0},{-1,0},{0,-1}};
1481 for( int j = 0; j < vg_list_size(dirs); j ++ )
1482 if( pcell( (v2i){ posx+dirs[j][0], posy+dirs[j][1] } )->state & FLAG_CANAL )
1483 {
1484 fish->dir[0] = dirs[j][0];
1485 fish->dir[1] = dirs[j][1];
1486 can_spawn = 1;
1487 break;
1488 }
1489
1490 if( !can_spawn )
1491 world.num_fishes--;
1492 else
1493 alive_count ++;
1494 }
1495 }
1496 }
1497
1498 if( alive_count == 0 )
1499 {
1500 world.completed = 1;
1501
1502 for( int i = 0; i < arrlen( world.io ); i ++ )
1503 {
1504 struct cell_terminal *term = &world.io[ i ];
1505 int is_input = world.data[ term->id ].state & FLAG_INPUT;
1506
1507 if( !is_input )
1508 {
1509 struct terminal_run *run = &term->runs[ world.sim_run ];
1510
1511 if( run->recv_count == run->condition_count )
1512 {
1513 for( int j = 0; j < run->condition_count; j ++ )
1514 {
1515 if( run->recieved[j] != run->conditions[j] )
1516 {
1517 world.completed = 0;
1518 break;
1519 }
1520 }
1521 }
1522 else
1523 {
1524 world.completed = 0;
1525 break;
1526 }
1527 }
1528 }
1529
1530 if( world.completed )
1531 {
1532 if( world.sim_run < world.max_runs-1 )
1533 {
1534 vg_success( "Run passed, starting next\n" );
1535 world.sim_run ++;
1536 world.sim_frame = 0;
1537 world.sim_start = vg_time;
1538 world.num_fishes = 0;
1539 continue;
1540 }
1541 else
1542 {
1543 vg_success( "Level passed!\n" );
1544
1545 u32 score = 0;
1546 for( int i = 0; i < world.w*world.h; i ++ )
1547 if( world.data[ i ].state & FLAG_CANAL )
1548 score ++;
1549
1550 world.score = score;
1551 world.time = world.sim_frame;
1552 }
1553 }
1554 else
1555 {
1556 vg_error( "Level failed :(\n" );
1557 }
1558
1559 // Copy into career data
1560 if( world.ptr_career_level )
1561 {
1562 world.ptr_career_level->score = world.score;
1563 world.ptr_career_level->time = world.time;
1564 world.ptr_career_level->completed = world.completed;
1565 }
1566
1567 simulation_stop(); // TODO: Async?
1568 break;
1569 }
1570
1571 world.sim_frame ++;
1572 }
1573
1574 float scaled_time = 0.0f;
1575 scaled_time = (vg_time-world.sim_start)*2.0f;
1576 world.frame_lerp = scaled_time - (float)world.sim_frame;
1577
1578 // Update positions
1579 for( int i = 0; i < world.num_fishes; i ++ )
1580 {
1581 struct fish *fish = &world.fishes[i];
1582
1583 if( fish->alive == 0 )
1584 continue;
1585
1586 if( fish->alive == -1 && (world.frame_lerp > fish->death_time) )
1587 continue; // Todo: particle thing?
1588
1589 if( fish->alive == -2 )
1590 {
1591 v2_muladds( fish->physics_v, (v2f){ 0.0, -9.8f }, vg_time_delta, fish->physics_v );
1592 v2_muladds( fish->physics_co, fish->physics_v, vg_time_delta, fish->physics_co );
1593 }
1594 else
1595 {
1596 struct cell *cell = pcell(fish->pos);
1597 v2f const *curve;
1598
1599 float t = world.frame_lerp;
1600
1601 v2_copy( fish->physics_co, fish->physics_v );
1602
1603 switch( cell->config )
1604 {
1605 case 13:
1606 if( fish->dir[0] == 1 )
1607 curve = curve_12;
1608 else
1609 curve = curve_9;
1610 break;
1611 case 1: curve = curve_1; break;
1612 case 4: curve = curve_4; break;
1613 case 2: curve = curve_2; break;
1614 case 8: curve = curve_8; break;
1615 case 3: curve = curve_3; break;
1616 case 6: curve = curve_6; break;
1617 case 9: curve = curve_9; break;
1618 case 12: curve = curve_12; break;
1619 case 7:
1620 if( t > curve_7_linear_section )
1621 {
1622 t -= curve_7_linear_section;
1623 t *= (1.0f/(1.0f-curve_7_linear_section));
1624
1625 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
1626 }
1627 else curve = NULL;
1628 break;
1629 default: curve = NULL; break;
1630 }
1631
1632 if( curve )
1633 {
1634 float t2 = t * t;
1635 float t3 = t * t * t;
1636
1637 float cA = 3.0f*t2 - 3.0f*t3;
1638 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
1639 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
1640
1641 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
1642 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
1643 fish->physics_co[0] += (float)fish->pos[0];
1644 fish->physics_co[1] += (float)fish->pos[1];
1645 }
1646 else
1647 {
1648 v2f origin;
1649 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
1650 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
1651
1652 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
1653 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
1654 }
1655 }
1656 }
1657 }
1658 }
1659
1660 static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
1661 {
1662 v2i full_start = { 0,0 };
1663 v2i full_end = { world.w, world.h };
1664
1665 if( !start || !end )
1666 {
1667 start = full_start;
1668 end = full_end;
1669 }
1670
1671 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1672
1673 for( int y = start[1]; y < end[1]; y ++ )
1674 {
1675 for( int x = start[0]; x < end[0]; x ++ )
1676 {
1677 struct cell *cell = pcell((v2i){x,y});
1678 int selected = world.selected == y*world.w + x;
1679
1680 int tile_offsets[][2] =
1681 {
1682 {2, 0}, {0, 3}, {0, 2}, {2, 2},
1683 {1, 0}, {2, 3}, {3, 2}, {1, 3},
1684 {3, 1}, {0, 1}, {1, 2}, {2, 1},
1685 {1, 1}, {3, 3}, {2, 1}, {2, 1}
1686 };
1687
1688 int uv[2] = { 3, 0 };
1689
1690 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1691 {
1692 uv[0] = tile_offsets[ cell->config ][0];
1693 uv[1] = tile_offsets[ cell->config ][1];
1694 } else continue;
1695
1696 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
1697 if( selected )
1698 {
1699 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
1700 draw_mesh( 0, 2 );
1701 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1702 }
1703 else
1704 draw_mesh( 0, 2 );
1705 }
1706 }
1707 }
1708
1709 static void draw_numbers( v3f coord, int number )
1710 {
1711 v3f pos;
1712 v3_copy( coord, pos );
1713 int digits[8]; int i = 0;
1714
1715 while( number > 0 && i < 8 )
1716 {
1717 digits[i ++] = number % 10;
1718 number = number / 10;
1719 }
1720
1721 for( int j = 0; j < i; j ++ )
1722 {
1723 glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, pos );
1724 draw_mesh( MESH_NUMBERS_OFFSETS[digits[i-j-1]][0], MESH_NUMBERS_OFFSETS[digits[i-j-1]][1] );
1725 pos[0] += pos[2] * 0.75f;
1726 }
1727 }
1728
1729 void vg_render(void)
1730 {
1731 glViewport( 0,0, vg_window_x, vg_window_y );
1732
1733 glDisable( GL_DEPTH_TEST );
1734 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
1735 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
1736
1737 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
1738 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
1739
1740 // TILE SET RENDERING
1741 // todo: just slam everything into a mesh...
1742 // when user modifies a tile the neighbours can be easily uploaded to gpu mem
1743 // in ~3 subBuffers
1744 // Currently we're uploading a fair amount of data every frame anyway.
1745 // NOTE: this is for final optimisations ONLY!
1746 // ======================================================================
1747
1748 use_mesh( &world.tile );
1749
1750 // Draw background
1751
1752 if(1){
1753
1754 SHADER_USE( shader_background );
1755 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1756
1757 glActiveTexture( GL_TEXTURE0 );
1758 glBindTexture( GL_TEXTURE_2D, world.background_data );
1759 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
1760
1761 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
1762 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
1763
1764 glActiveTexture( GL_TEXTURE1 );
1765 glBindTexture( GL_TEXTURE_2D, world.random_samples );
1766 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
1767
1768 draw_mesh( 0, 2 );
1769
1770 }
1771
1772
1773 SHADER_USE( shader_tile_main );
1774
1775 m2x2f subtransform;
1776 m2x2_identity( subtransform );
1777 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1778 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1779 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
1780 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
1781
1782 glEnable(GL_BLEND);
1783 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1784 glBlendEquation(GL_FUNC_ADD);
1785
1786 // Bind textures
1787 vg_tex2d_bind( &tex_tile_data, 0 );
1788 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
1789
1790 vg_tex2d_bind( &tex_wood, 1 );
1791 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
1792
1793 render_tiles( NULL, NULL, colour_default, colour_default );
1794
1795
1796
1797 SHADER_USE( shader_ball );
1798 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1799
1800 vg_tex2d_bind( &tex_ball, 0 );
1801 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
1802
1803 // Draw 'fish'
1804 if( world.simulating )
1805 {
1806 for( int i = 0; i < world.num_fishes; i ++ )
1807 {
1808 struct fish *fish = &world.fishes[i];
1809
1810 if( fish->alive == 0 )
1811 continue;
1812
1813 if( fish->alive == -1 && (world.frame_lerp > fish->death_time) )
1814 continue;
1815
1816 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
1817 colour_code_v3( fish->payload, dot_colour );
1818
1819 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
1820 glUniform2fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, fish->physics_co );
1821 draw_mesh( 0, 32 );
1822 }
1823 }
1824
1825 SHADER_USE( shader_tile_main );
1826
1827 // Bind textures
1828 vg_tex2d_bind( &tex_tile_data, 0 );
1829 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
1830
1831 vg_tex2d_bind( &tex_wood, 1 );
1832 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
1833
1834 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
1835 render_tiles( NULL, NULL, colour_default, colour_selected );
1836
1837 // Draw splitters
1838 for( int y = 2; y < world.h-2; y ++ )
1839 {
1840 for( int x = 2; x < world.w-2; x ++ )
1841 {
1842 struct cell *cell = pcell((v2i){x,y});
1843
1844 if( cell->state & FLAG_CANAL )
1845 {
1846 if( cell->config == k_cell_type_split )
1847 {
1848 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
1849
1850 if( cell->state & FLAG_FLIP_ROTATING )
1851 {
1852 if( (world.frame_lerp > curve_7_linear_section) )
1853 {
1854 float const rotation_speed = 0.4f;
1855 if( (world.frame_lerp < 1.0f-rotation_speed) )
1856 {
1857 float t = world.frame_lerp - curve_7_linear_section;
1858 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
1859 t += 1.0f;
1860
1861 rotation *= t;
1862 }
1863 else
1864 rotation *= -1.0f;
1865 }
1866 }
1867
1868 m2x2_create_rotation( subtransform, rotation );
1869
1870 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1871 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y + 0.125f, cell->state & FLAG_TARGETED? 3.0f: 0.0f, 0.0f );
1872 draw_mesh( 0, 2 );
1873 }
1874 }
1875 }
1876 }
1877
1878 // Edit overlay
1879 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) )
1880 {
1881 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
1882 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
1883
1884 world.data[ world.selected ].state ^= FLAG_CANAL;
1885 map_reclassify( new_begin, new_end, 0 );
1886
1887 m2x2_identity( subtransform );
1888 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
1889 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
1890 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
1891
1892 render_tiles( new_begin, new_end, colour_default, colour_default );
1893
1894 world.data[ world.selected ].state ^= FLAG_CANAL;
1895 map_reclassify( new_begin, new_end, 0 );
1896 }
1897
1898 //glDisable(GL_BLEND);
1899
1900 // Draw connecting wires
1901 glDisable(GL_BLEND);
1902
1903 SHADER_USE( shader_wire );
1904 glBindVertexArray( world.wire.vao );
1905
1906 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1907 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
1908 glUniform4f( SHADER_UNIFORM( shader_wire, "uColour" ), 0.2f, 0.2f, 0.2f, 1.0f );
1909
1910 for( int y = 2; y < world.h-2; y ++ )
1911 {
1912 for( int x = 2; x < world.w-2; x ++ )
1913 {
1914 struct cell *cell = pcell((v2i){x,y});
1915
1916 if( cell->state & FLAG_CANAL )
1917 {
1918 if( cell->state & FLAG_IS_TRIGGER )
1919 {
1920 int trigger_id = cell->links[0]?0:1;
1921
1922 int x2 = cell->links[trigger_id] % world.w;
1923 int y2 = (cell->links[trigger_id] - x2) / world.w;
1924
1925 v2f startpoint;
1926 v2f endpoint;
1927
1928 startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
1929 startpoint[1] = (float)y2 + 0.25f;
1930
1931 endpoint[0] = x+0.5f;
1932 endpoint[1] = y+0.5f;
1933
1934 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.04f );
1935 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.04f );
1936 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
1937 }
1938 }
1939 }
1940 }
1941
1942 SHADER_USE( shader_tile_colour );
1943 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
1944 use_mesh( &world.circle );
1945
1946 int const filled_start = 0;
1947 int const filled_count = 32;
1948 int const empty_start = 32;
1949 int const empty_count = 32*2;
1950
1951 // Draw i/o arrays
1952 for( int i = 0; i < arrlen( world.io ); i ++ )
1953 {
1954 struct cell_terminal *term = &world.io[ i ];
1955 int posx = term->id % world.w;
1956 int posy = (term->id - posx)/world.w;
1957 int is_input = world.data[ term->id ].state & FLAG_INPUT;
1958
1959 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
1960
1961 for( int k = 0; k < term->run_count; k ++ )
1962 {
1963 for( int j = 0; j < term->runs[k].condition_count; j ++ )
1964 {
1965 float y_offset = is_input? 1.2f: -0.2f;
1966 y_offset += (is_input? 0.2f: -0.2f) * (float)k;
1967
1968 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)posx + 0.2f + 0.2f * (float)j, (float)posy + y_offset, 0.1f );
1969
1970 if( is_input )
1971 {
1972 colour_code_v3( term->runs[k].conditions[j], dot_colour );
1973 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1974
1975 // Draw filled if tick not passed, draw empty if empty
1976 if( world.sim_frame > j && world.sim_run >= k )
1977 draw_mesh( empty_start, empty_count );
1978 else
1979 draw_mesh( filled_start, filled_count );
1980 }
1981 else
1982 {
1983 if( term->runs[k].recv_count > j )
1984 {
1985 colour_code_v3( term->runs[k].recieved[j], dot_colour );
1986 v3_muls( dot_colour, 0.8f, dot_colour );
1987 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1988
1989 draw_mesh( filled_start, filled_count );
1990 }
1991
1992 colour_code_v3( term->runs[k].conditions[j], dot_colour );
1993 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
1994
1995 draw_mesh( empty_start, empty_count );
1996 }
1997 }
1998 }
1999 }
2000
2001 if( world.simulating )
2002 {
2003 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.0f, 0.0f, 0.0f, 1.0f );
2004 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 );
2005 draw_mesh( filled_start, filled_count );
2006 }
2007
2008 // Draw score
2009 float const score_bright = 1.25f;
2010 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2011 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
2012
2013 use_mesh( &world.numbers );
2014 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
2015
2016 // Level selection UI
2017 use_mesh( &world.circle );
2018 float ratio = ((float)vg_window_x/(float)vg_window_y);
2019
2020 m3x3f ui_view = M3X3_IDENTITY;
2021 m3x3_scale( ui_view, (v3f){ 1.0f, ratio, 1.0f } );
2022 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)ui_view );
2023
2024 // Calculate mouse in UIsp
2025 v3f mouse_ui_space = { ((float)vg_mouse[0] / (float)(vg_window_x)) * 2.0f - 1.0f,
2026 (((float)vg_mouse[1] / (float)(vg_window_y)) * 2.0f - 1.0f)*(-1.0f/ratio), 0.0125f };
2027
2028 // Get selected level
2029 const float selection_scale = 0.05f;
2030 int const level_count = vg_list_size( level_pack_1 );
2031 int level_select = -1;
2032
2033 if( mouse_ui_space[0] <= -0.8f )
2034 {
2035 float levels_range = (float)level_count*selection_scale*0.6f;
2036 float level_offset = ((-mouse_ui_space[1] + levels_range) / levels_range) * 0.5f * (float)level_count;
2037 level_select = ceilf( level_offset );
2038
2039 // Draw selector
2040 if( level_select >= 0 && level_select < vg_list_size( level_pack_1 ) )
2041 {
2042 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.369768f, 0.3654f, 0.42f, 1.0f );
2043
2044 use_mesh( &world.tile );
2045 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2046 -1.0f,
2047 ((float)level_count - (float)level_select * 2.0f ) * selection_scale * 0.6f,
2048 selection_scale
2049 );
2050 draw_mesh( 2, 2 );
2051
2052 use_mesh( &world.circle );
2053
2054 if( vg_get_button_down( "primary" ) )
2055 {
2056 console_changelevel( 1, level_pack_1 + level_select );
2057 }
2058 }
2059 }
2060 else mouse_ui_space[1] = INFINITY;
2061
2062 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f, 0.39f, 0.45f, 1.0f );
2063
2064 // Draw levels
2065 for( int i = 0; i < level_count; i ++ )
2066 {
2067 struct career_level *clevel = &career.levels[i];
2068
2069 v3f level_ui_space = {
2070 -0.97f,
2071 ((float)level_count - (float)i * 2.0f ) * selection_scale * 0.6f + selection_scale * 0.5f,
2072 selection_scale * 0.5f
2073 };
2074
2075 float scale = vg_clampf( 1.0f - fabsf(level_ui_space[1] - mouse_ui_space[1]) * 2.0f, 0.9f, 1.0f );
2076 level_ui_space[2] *= scale;
2077
2078 glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, level_ui_space );
2079
2080 if( clevel->completed )
2081 draw_mesh( filled_start, filled_count );
2082 else
2083 draw_mesh( empty_start, empty_count );
2084 }
2085
2086 // Level scores
2087 use_mesh( &world.numbers );
2088 for( int i = 0; i < level_count; i ++ )
2089 {
2090 struct career_level *clevel = &career.levels[i];
2091
2092 v3f level_ui_space = {
2093 -0.94f,
2094 ((float)level_count - (float)i * 2.0f ) * selection_scale * 0.6f + selection_scale * 0.5f,
2095 0.02f
2096 };
2097
2098 if( clevel->completed )
2099 {
2100 draw_numbers( level_ui_space, clevel->score );
2101 }
2102 }
2103
2104 //use_mesh( &world.numbers );
2105 //draw_numbers( (v3f){ 0.0f, -0.5f, 0.1f }, 128765 );
2106 }
2107
2108 void vg_ui(void)
2109 {
2110 //ui_test();
2111 }