1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
6 SHADER_DEFINE( shader_tile_colour
,
9 "layout (location=0) in vec2 a_co;"
11 "uniform vec3 uOffset;"
15 "gl_Position = vec4( uPv * vec3( a_co * uOffset.z + uOffset.xy, 1.0 ), 1.0 );"
20 "uniform vec4 uColour;"
24 "FragColor = uColour;"
27 UNIFORMS({ "uPv", "uOffset", "uColour" })
34 #define FLAG_INPUT 0x1
35 #define FLAG_OUTPUT 0x2
36 #define FLAG_CANAL 0x4
38 #define FLAG_DROP_L 0x10
39 #define FLAG_SPLIT 0x20
40 #define FLAG_MERGER 0x40
41 #define FLAG_DROP_R 0x80
73 static void map_free(void)
75 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
76 arrfree( world
.io
[ i
].conditions
);
78 arrfree( world
.data
);
87 static int map_load( const char *str
)
96 if( str
[world
.w
] == ';' )
98 else if( !str
[world
.w
] )
100 vg_error( "Unexpected EOF when parsing level\n" );
105 struct cell
*row
= arraddnptr( world
.data
, world
.w
);
107 int reg_start
= 0, reg_end
= 0;
123 if( reg_start
< reg_end
)
125 if( *c
>= 'a' && *c
<= 'z' )
127 arrpush( world
.io
[ reg_start
].conditions
, *c
);
131 if( *c
== ',' || *c
== '\n' )
140 vg_error( "Unkown attribute '%c' (row: %u)\n", *c
, world
.h
);
147 vg_error( "Too many values to assign (row: %u)\n", world
.h
);
155 if( reg_start
!= reg_end
)
157 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world
.h
, reg_start
, reg_end
);
163 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world
.h
, cx
, world
.w
);
167 row
= arraddnptr( world
.data
, world
.w
);
170 reg_end
= reg_start
= arrlen( world
.io
);
176 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world
.h
, cx
, world
.w
);
180 // Tile initialization
182 row
[ cx
].water
[0] = 0;
183 row
[ cx
].water
[1] = 0;
185 if( *c
== '+' || *c
== '-' )
187 struct cell_terminal term
= { .id
= cx
+ world
.h
*world
.w
};
188 arrpush( world
.io
, term
);
189 row
[ cx
++ ].state
= *c
== '+'? FLAG_INPUT
: FLAG_OUTPUT
;
194 row
[ cx
++ ].state
= FLAG_WALL
;
198 row
[ cx
++ ].state
= 0x00;
205 vg_success( "Map loaded! (%u:%u)\n", world
.w
, world
.h
);
209 int main( int argc
, char *argv
[] )
211 vg_init( argc
, argv
, "FishLadder" );
214 void vg_register(void)
216 SHADER_INIT( shader_tile_colour
);
223 glGenVertexArrays( 1, &world
.tile_vao
);
224 glGenBuffers( 1, &world
.tile_vbo
);
228 0.01f
, 0.01f
, 0.01f
, 0.99f
, 0.99f
, 0.99f
,
229 0.01f
, 0.01f
, 0.99f
, 0.99f
, 0.99f
, 0.01f
,
231 0.48f
, 0.48f
, 0.5f
, 0.52f
, 0.52f
, 0.52f
, // Static dot
232 0.375f
, 0.25f
, 0.5f
, 0.75f
, 0.625f
, 0.25f
, // Downwards pointing arrow
233 0.25f
, 0.625f
, 0.75f
, 0.5f
, 0.25f
, 0.375f
, // Left
234 0.625f
, 0.75f
, 0.5f
, 0.25f
, 0.375f
, 0.75f
, // up
235 0.75f
, 0.375f
, 0.25f
, 0.5f
, 0.75f
, 0.625f
238 glBindVertexArray( world
.tile_vao
);
239 glBindBuffer( GL_ARRAY_BUFFER
, world
.tile_vbo
);
248 glVertexAttribPointer( 0, 2, GL_FLOAT
, GL_FALSE
, 2 * sizeof(float), (void*)0 );
249 glEnableVertexAttribArray( 0 );
256 glGenVertexArrays( 1, &world
.circle_vao
);
257 glGenBuffers( 1, &world
.circle_vbo
);
259 float circle_mesh
[32*6*3];
260 int res
= vg_list_size( circle_mesh
) / (6*3);
262 for( int i
= 0; i
< res
; i
++ )
264 v2f v0
= { sinf( ((float)i
/(float)res
)*VG_TAUf
), cosf( ((float)i
/(float)res
)*VG_TAUf
) };
265 v2f v1
= { sinf( ((float)(i
+1)/(float)res
)*VG_TAUf
), cosf( ((float)(i
+1)/(float)res
)*VG_TAUf
) };
267 circle_mesh
[ i
*6+0 ] = 0.0f
;
268 circle_mesh
[ i
*6+1 ] = 0.0f
;
270 v2_copy( v0
, circle_mesh
+ 32*6 + i
*12 );
271 v2_muls( v0
, 0.8f
, circle_mesh
+ 32*6 + i
*12+2 );
272 v2_copy( v1
, circle_mesh
+ 32*6 + i
*12+4 );
274 v2_copy( v1
, circle_mesh
+ 32*6 + i
*12+6 );
275 v2_muls( v1
, 0.8f
, circle_mesh
+ 32*6 + i
*12+8 );
276 v2_muls( v0
, 0.8f
, circle_mesh
+ 32*6 + i
*12+10 );
278 v2_copy( v0
, circle_mesh
+ i
*6+4 );
279 v2_copy( v1
, circle_mesh
+ i
*6+2 );
280 v2_copy( v0
, circle_mesh
+i
*6+4 );
281 v2_copy( v1
, circle_mesh
+i
*6+2 );
284 glBindVertexArray( world
.circle_vao
);
285 glBindBuffer( GL_ARRAY_BUFFER
, world
.circle_vbo
);
286 glBufferData( GL_ARRAY_BUFFER
, sizeof( circle_mesh
), circle_mesh
, GL_STATIC_DRAW
);
288 glVertexAttribPointer( 0, 2, GL_FLOAT
, GL_FALSE
, 2 * sizeof(float), (void*)0 );
289 glEnableVertexAttribArray( 0 );
297 "###-#####-###;aaa,aa\n"
304 "###+#####+###;aa,aaa\n"
311 glDeleteVertexArrays( 1, &world
.tile_vao
);
312 glDeleteBuffers( 1, &world
.tile_vbo
);
314 glDeleteVertexArrays( 1, &world
.circle_vao
);
315 glDeleteBuffers( 1, &world
.circle_vbo
);
320 static int cell_interactive( v2i co
)
323 if( co
[0] < 2 || co
[0] >= world
.w
-2 || co
[1] < 2 || co
[1] >= world
.h
-2 )
327 if( world
.data
[ world
.w
*co
[1] + co
[0] ].state
& (FLAG_WALL
|FLAG_INPUT
|FLAG_OUTPUT
) )
330 // List of 3x3 configurations that we do not allow
331 static u32 invalid_src
[][9] =
363 // Statically compile invalid configurations into bitmasks
364 static u32 invalid
[ vg_list_size(invalid_src
) ];
366 for( int i
= 0; i
< vg_list_size(invalid_src
); i
++ )
370 for( int j
= 0; j
< 3; j
++ )
371 for( int k
= 0; k
< 3; k
++ )
372 comped
|= invalid_src
[i
][ j
*3+k
] << ((j
*5)+k
);
377 // Extract 5x5 grid surrounding tile
379 for( int y
= co
[1]-2; y
< co
[1]+3; y
++ )
380 for( int x
= co
[0]-2; x
< co
[0]+3; x
++ )
382 struct cell
*cell
= &world
.data
[ world
.w
*y
+ x
];
384 if( cell
&& (cell
->state
& (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
)) )
385 blob
|= 0x1 << ((y
-(co
[1]-2))*5 + x
-(co
[0]-2));
388 // Run filter over center 3x3 grid to check for invalid configurations
389 int kernel
[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
390 for( int i
= 0; i
< vg_list_size(kernel
); i
++ )
392 if( blob
& (0x1 << (6+kernel
[i
])) )
394 u32 window
= blob
>> kernel
[i
];
396 for( int j
= 0; j
< vg_list_size(invalid
); j
++ )
397 if((window
& invalid
[j
]) == invalid
[j
])
407 float ratio
= (float)vg_window_y
/ (float)vg_window_x
;
408 float const size
= 9.5f
;
411 origin
[0] = -0.5f
* world
.w
;
412 origin
[1] = -0.5f
* world
.h
;
415 m3x3_projection( m_projection
, -size
, size
, size
*ratio
, -size
*ratio
);
416 m3x3_identity( m_view
);
417 m3x3_translate( m_view
, origin
);
418 m3x3_mul( m_projection
, m_view
, vg_pv
);
419 vg_projection_update();
422 v2_copy( vg_mouse_ws
, tile_pos
);
424 int tile_x
= floorf( tile_pos
[0] );
425 int tile_y
= floorf( tile_pos
[1] );
427 if( cell_interactive( (v2i
){ tile_x
, tile_y
} ))
429 world
.selected
= tile_y
* world
.w
+ tile_x
;
431 if( vg_get_button_down("primary") )
433 world
.data
[ world
.selected
].state
^= FLAG_CANAL
;
439 // Reclassify world. TODO: Move into own function
440 for( int y
= 2; y
< world
.h
-2; y
++ )
442 for( int x
= 2; x
< world
.w
-2; x
++ )
444 v2i dirs
[] = {{1,0},{0,1},{-1,0},{0,-1}};
448 if( world
.data
[y
*world
.w
+x
].state
& FLAG_CANAL
)
450 for( int i
= 0; i
< vg_list_size( dirs
); i
++ )
452 struct cell
*neighbour
= &world
.data
[(y
+dirs
[i
][1])*world
.w
+x
+dirs
[i
][0]];
453 if( neighbour
->state
& (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
) )
458 world
.data
[ y
*world
.w
+x
].config
= config
;
459 world
.data
[ y
*world
.w
+x
].state
&= ~(FLAG_DROP_L
|FLAG_DROP_R
|FLAG_SPLIT
|FLAG_MERGER
);
463 for( int y
= 2; y
< world
.h
-2; y
++ )
464 for( int x
= 2; x
< world
.w
-2; x
++ )
466 // R,D,L,- 1110 (splitter, 1 drop created)
468 // R,-,L,U - 1011 (merger, 2 drop created)
470 u8 config
= world
.data
[y
*world
.w
+x
].config
;
472 if( config
== 0x7 ) // splitter
474 world
.data
[y
*world
.w
+x
].state
|= (FLAG_SPLIT
| FLAG_DROP_L
| FLAG_DROP_R
);
476 else if( config
== 0xD )
478 world
.data
[y
*world
.w
+x
-1].state
|= FLAG_DROP_R
;
479 world
.data
[y
*world
.w
+x
+1].state
|= FLAG_DROP_L
;
480 world
.data
[y
*world
.w
+x
].state
|= FLAG_MERGER
;
497 configs (Downlevels marked)
509 Path tracing with min function
521 Path tracing with min function
547 static int update_tick
= 0;
550 if( update_tick
> 5 )
554 u32 buffer_id
= world
.frame
& 0x1;
555 u32 buffer_next
= buffer_id
^ 0x1;
557 for( int y
= 1; y
< world
.h
-1; y
++ )
559 for( int x
= 1; x
< world
.w
-1; x
++ )
561 struct cell
*cell
= &world
.data
[y
*world
.w
+x
];
563 if( cell
->state
& FLAG_OUTPUT
)
564 cell
->water
[ buffer_next
] = 16;
568 cell
->water
[ buffer_next
] = 0;
570 if( cell
->state
& FLAG_CANAL
)
572 v2i dirs
[] = {{1,0},{-1,0},{0,-1}};
574 for( int i
= 0; i
< vg_list_size( dirs
); i
++ )
576 struct cell
*neighbour
= &world
.data
[(y
+dirs
[i
][1])*world
.w
+x
+dirs
[i
][0]];
578 // Non canals will be ignored
579 if( !(neighbour
->state
& (FLAG_CANAL
|FLAG_OUTPUT
)) )
582 // Only vertical pulls allowed on neighbour splitters
583 if( (neighbour
->state
& FLAG_SPLIT
) && i
!= 2 )
586 // Only vertical pulls allowed for mergers
587 if( (cell
->state
& FLAG_MERGER
) && i
!= 2 )
590 // Test for renewall cases if we have drop L/R check if i matches direction.
591 if( (((cell
->state
& FLAG_DROP_L
)&&i
==1) || ((cell
->state
& FLAG_DROP_R
)&&i
==0)) && neighbour
->water
[ buffer_id
] )
593 cell
->water
[ buffer_next
] = 16;
597 if( neighbour
->water
[ buffer_id
] > cell
->water
[ buffer_next
]+1 )
600 cell
->water
[ buffer_next
] = neighbour
->water
[ buffer_id
]-1;
605 if( !has_source
&& cell
->water
[ buffer_id
] )
606 cell
->water
[ buffer_next
] = cell
->water
[ buffer_id
]-1;
617 glViewport( 0,0, vg_window_x
, vg_window_y
);
619 glDisable( GL_DEPTH_TEST
);
620 glClearColor( 0.01f
, 0.01f
, 0.01f
, 1.0f
);
621 glClear( GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
623 glBindVertexArray( world
.tile_vao
);
624 SHADER_USE( shader_tile_colour
);
625 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
627 for( int y
= 0; y
< world
.h
; y
++ )
629 for( int x
= 0; x
< world
.w
; x
++ )
631 glUniform3f( SHADER_UNIFORM( shader_tile_colour
, "uOffset" ), (float)x
, (float)y
, 1.0f
);
635 struct cell
*cell
= &world
.data
[y
*world
.w
+x
];
637 if( cell
->state
& FLAG_WALL
) { v4_copy( (v4f
){ 0.2f
, 0.2f
, 0.2f
, 1.0f
}, colour
); }
638 else if( cell
->state
& FLAG_CANAL
) { v4_copy( (v4f
){ 0.6f
, 0.6f
, 0.6f
, 1.0f
}, colour
); }
639 else if( cell
->state
& FLAG_INPUT
) { v4_copy( (v4f
){ 0.5f
, 0.5f
, 0.5f
, 1.0f
}, colour
); }
640 else if( cell
->state
& FLAG_OUTPUT
) { v4_copy( (v4f
){ 0.2f
, 0.7f
, 0.3f
, 1.0f
}, colour
); }
641 else v4_copy( (v4f
){ 0.9f
, 0.9f
, 0.9f
, 1.0f
}, colour
);
643 if( cell
->water
[world
.frame
&0x1] )
644 v4_copy( (v4f
){ 0.2f
, 0.3f
, 0.7f
* (float)(cell
->water
[world
.frame
&0x1]) * (1.0f
/16.0f
), 1.0f
}, colour
);
646 if( world
.selected
== y
*world
.w
+ x
)
647 v3_muls( colour
, sinf( vg_time
)*0.25f
+ 0.5f
, colour
);
649 //if( cell->state & (FLAG_SPLIT) )
650 // v4_copy( (v4f){ 0.75f, 0.75f, 0.02f, 1.0f }, colour );
651 //if( cell->state & (FLAG_MERGER) )
652 // v4_copy( (v4f){ 0.75f, 0.02f, 0.75f, 1.0f }, colour );
654 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, colour
);
656 glDrawArrays( GL_TRIANGLES
, 0, 6 );
658 if( cell
->state
& FLAG_CANAL
)
660 glUniform4f( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1.0f
, 1.0f
, 1.0f
, 1.0f
);
661 glDrawArrays( GL_TRIANGLES
, 6, 3 );
666 glBindVertexArray( world
.circle_vao
);
669 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
671 struct cell_terminal
*term
= &world
.io
[ i
];
672 int posx
= term
->id
% world
.w
;
673 int posy
= (term
->id
- posx
)/world
.w
;
674 int is_input
= world
.data
[ term
->id
].state
& FLAG_INPUT
;
676 for( int j
= 0; j
< arrlen( term
->conditions
); j
++ )
678 glUniform3f( SHADER_UNIFORM( shader_tile_colour
, "uOffset" ), (float)posx
+ 0.2f
+ 0.2f
* (float)j
, (float)posy
+ 0.2f
, 0.1f
);
679 glUniform4f( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 0.2f
, 0.8f
, 0.6f
, 1.0f
);
680 glDrawArrays( GL_TRIANGLES
, is_input
? 0: 32*3, is_input
? 32*3: 32*6 );