level smooth transition camera
[fishladder.git] / fishladder.c
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2
3 //#define VG_CAPTURE_MODE
4 #define VG_STEAM
5 #define VG_STEAM_APPID 1218140U
6 #include "vg/vg.h"
7 #include "fishladder_resources.h"
8
9 // #define STEAM_LEADERBOARDS
10
11 // CONSTANTS
12 // ===========================================================================================================
13
14 enum cell_type
15 {
16 k_cell_type_stub = 0,
17 k_cell_type_ramp_right = 3,
18 k_cell_type_ramp_left = 6,
19 k_cell_type_split = 7,
20 k_cell_type_merge = 13,
21 k_cell_type_con_r = 1,
22 k_cell_type_con_u = 2,
23 k_cell_type_con_l = 4,
24 k_cell_type_con_d = 8
25 };
26
27 enum e_fish_state
28 {
29 k_fish_state_soon_dead = -1,
30 k_fish_state_dead = 0,
31 k_fish_state_alive,
32 k_fish_state_bg,
33 k_fish_state_soon_alive
34 };
35
36 enum e_world_button
37 {
38 k_world_button_none = -1,
39 k_world_button_sim = 0,
40 k_world_button_pause = 1,
41 k_world_button_speedy = 2
42 };
43
44 #define FLAG_CANAL 0x1
45 #define FLAG_IS_TRIGGER 0x2
46 #define FLAG_RESERVED0 0x4
47 #define FLAG_RESERVED1 0x8
48
49 #define FLAG_INPUT 0x10
50 #define FLAG_OUTPUT 0x20
51 #define FLAG_WALL 0x40
52
53 #define FLAG_FLIP_FLOP 0x100
54 #define FLAG_TRIGGERED 0x200
55 #define FLAG_FLIP_ROTATING 0x400
56 #define FLAG_TARGETED 0x800
57
58 /*
59 0000 0 | 0001 1 | 0010 2 | 0011 3
60 | | | | |
61 X | X= | X | X=
62 | | |
63 0100 4 | 0101 5 | 0110 6 | 0111 7
64 | | | | |
65 =X | =X= | =X | =X=
66 | | |
67 1000 8 | 1001 9 | 1010 10 | 1011 11
68 | | | | |
69 X | X= | X | X=
70 | | | | | | |
71 1100 12 | 1101 13 | 1110 14 | 1111 15
72 | | | | |
73 =X | =X= | =X | =X=
74 | | | | | | |
75 */
76
77 static struct cell_description
78 {
79 v2i start;
80 v2i end;
81
82 int is_special;
83 int is_linear;
84
85 v2f trigger_pos;
86 }
87 cell_descriptions[] =
88 {
89 // 0-3
90 {},
91 { .start = { 1, 0 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f } },
92 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f } },
93 { .start = { 0, 1 }, .end = { 1, 0 }, .trigger_pos = { 0.25f, 0.25f } },
94 // 4-7
95 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f } },
96 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f }, .is_linear = 1 },
97 { .start = { 0, 1 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f } },
98 { .start = { 0, 1 }, .is_special = 1 },
99 // 8-11
100 { .start = { 0, -1 }, .end = { 0, 1 }, .trigger_pos = { 0.25f, 0.5f } },
101 { .start = { 1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.75f } },
102 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f }, .is_linear = 1 },
103 { },
104 // 12-15
105 { .start = { -1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.75f, 0.75f } },
106 { .end = { 0, -1 }, .is_special = 1, .trigger_pos = { 0.5f, 0.75f } },
107 { },
108 { }
109 };
110
111 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
112 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
113 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
114 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
115
116 v2f const curve_1[] = {{1.0f,0.5f},{0.8f,0.5f},{0.3f,0.5f},{0.2f,0.5f}};
117 v2f const curve_4[] = {{0.0f,0.5f},{0.3f,0.5f},{0.5f,0.5f},{0.8f,0.5f}};
118 v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}};
119 v2f const curve_8[] = {{0.5f,0.0f},{0.5f,0.3f},{0.5f,0.5f},{0.5f,0.8f}};
120
121 v2f const curve_7[] = {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
122 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}};
123
124 float const curve_7_linear_section = 0.1562f;
125
126 // Types
127 // ===========================================================================================================
128
129 struct mesh
130 {
131 GLuint vao, vbo;
132 u32 elements;
133 };
134
135 struct world
136 {
137 #pragma pack(push,1)
138 struct cell
139 {
140 u16 state;
141 u16 links[2];
142 u8 config;
143 u8 pad0;
144 }
145 *data;
146 #pragma pack(pop)
147
148
149 int initialzed;
150
151 int sim_run, max_runs;
152
153 int sim_frame, sim_target;
154 float sim_internal_time, // current tick-time
155 sim_internal_delta, // time delta
156 sim_internal_ref, // Reference point of internal time
157 sim_delta_ref, // Reference point of vg_time when we started at current sim_speed
158 sim_delta_speed, // Rate to apply time delta
159 frame_lerp, // Fractional part of sim_internal_time
160 pause_offset_target; //
161
162 struct cell_terminal
163 {
164 struct terminal_run
165 {
166 char conditions[8];
167 char recieved[8];
168
169 int condition_count, recv_count;
170 }
171 runs[8];
172
173 int run_count;
174 v2i pos;
175 //int id;
176 }
177 *io;
178
179 struct cell_button
180 {
181 float light_target, light;
182 int pressed;
183 }
184 buttons[4];
185
186 int w, h;
187
188 struct mesh shapes;
189 struct mesh_wire
190 {
191 GLuint vao, vbo, ebo;
192 u32 em;
193 }
194 wire;
195
196 GLuint background_data;
197 GLuint random_samples;
198
199 int selected, tile_x, tile_y;
200 v2f tile_pos;
201
202 struct fish
203 {
204 v2i pos;
205 v2i dir;
206 enum e_fish_state state;
207 char payload;
208 int flow_reversed;
209 float death_time;
210 v2f physics_v;
211 v2f physics_co;
212 }
213 fishes[16];
214
215 int num_fishes;
216
217 char map_name[64];
218 //struct career_level *ptr_career_level;
219 struct cmp_level *pCmpLevel;
220
221 u32 score;
222 u32 completed;
223 u32 time;
224
225 u16 id_drag_from;
226 v2f drag_from_co;
227 v2f drag_to_co;
228 };
229
230 // Forward declerations
231 // --------------------
232
233 static int console_credits( int argc, char const *argv[] );
234 static int console_save_map( int argc, char const *argv[] );
235 static int console_load_map( int argc, char const *argv[] );
236 static int console_changelevel( int argc, char const *argv[] );
237
238 // GLOBALS
239 // ===========================================================================================================
240
241 m3x3f m_projection;
242 m3x3f m_view;
243 m3x3f m_mdl;
244
245 struct world world;
246
247 // UTILITY
248 // ===========================================================================================================
249
250 static void colour_code_v3( char const cc, v3f target )
251 {
252 static v3f colour_sets[] =
253 { { 1.0f, 0.9f, 0.3f },
254 { 0.2f, 0.9f, 0.14f },
255 { 0.4f, 0.8f, 1.00f },
256 { 0.882f, 0.204f, 0.922f }
257 };
258
259 if( cc >= 'a' && cc <= 'z' )
260 {
261 int id = cc - 'a';
262
263 if( id < vg_list_size( colour_sets ) )
264 {
265 v3_copy( colour_sets[ id ], target );
266 return;
267 }
268 }
269
270 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
271 }
272
273 static int hash21i( v2i p, u32 umod )
274 {
275 static const int random_noise[] =
276 {
277 0x46,0xD5,0xB8,0xD3,0xF2,0xE5,0xCC,0x07,0xD0,0xB3,0x7A,0xA2,0xC3,0xDA,0xDC,0x7F,0xE0,0xB7,0x42,0xA0,0xBF,0x41,0x92,0x32,0x6F,0x0D,0x45,0xC7,0x54,0xDB,0x30,0xC2,
278 0xD5,0xDA,0x55,0x09,0xDE,0x74,0x48,0x20,0xE1,0x24,0x5C,0x4D,0x6F,0x36,0xD8,0xE9,0x8D,0x8F,0x54,0x99,0x98,0x51,0xFE,0xDB,0x26,0x04,0x65,0x57,0x56,0xF3,0x53,0x30,
279 0x3D,0x16,0xC0,0xB6,0xF2,0x47,0xCF,0x62,0xB0,0x6C,0x8F,0x4F,0x8C,0x4C,0x17,0xF0,0x19,0x7E,0x2D,0x81,0x8D,0xFB,0x10,0xD3,0x49,0x50,0x60,0xFD,0x38,0x15,0x3B,0xEE,
280 0x05,0xC1,0xCF,0x62,0x97,0x75,0xDF,0x4E,0x4D,0x89,0x5E,0x88,0x5C,0x30,0x8C,0x54,0x1D,0x39,0x41,0xEA,0xA2,0x63,0x12,0x1B,0x8E,0x35,0x22,0x9B,0x98,0xA3,0x7F,0x80,
281 0xD6,0x27,0x94,0x66,0xB5,0x1D,0x7E,0xDF,0x96,0x28,0x38,0x3A,0xA0,0xE8,0x71,0x09,0x62,0x5E,0x9D,0x53,0x58,0x1B,0x7D,0x0D,0x2D,0x99,0x77,0x83,0xC3,0x89,0xC2,0xA2,
282 0xA7,0x1D,0x78,0x80,0x37,0xC1,0x87,0xFF,0x65,0xBF,0x2C,0xF1,0xE5,0xB3,0x09,0xE0,0x25,0x92,0x83,0x0F,0x8A,0x57,0x3C,0x0B,0xC6,0xBC,0x44,0x16,0xE3,0xCE,0xC3,0x0D,
283 0x69,0xD3,0xC6,0x99,0xB8,0x46,0x44,0xC4,0xF3,0x1E,0xBF,0xF5,0xB4,0xDB,0xFB,0x93,0xA1,0x7B,0xC9,0x08,0x77,0x22,0xE5,0x02,0xEF,0x9E,0x90,0x94,0x8A,0xA6,0x3D,0x7E,
284 0xA2,0xA0,0x10,0x82,0x47,0x5C,0xAA,0xF8,0x2F,0x0D,0x9F,0x76,0xDA,0x99,0x0F,0xCB,0xE2,0x02,0x0C,0x75,0xCA,0x35,0x29,0xA6,0x49,0x83,0x6D,0x91,0xB4,0xEC,0x31,0x69,
285 0xBA,0x13,0xF3,0xC7,0x21,0x06,0xC8,0x79,0xEF,0xB1,0x9C,0x6A,0xEE,0x64,0x9A,0xDC,0x1E,0xC6,0x18,0x93,0xA9,0x7E,0x89,0x7D,0x96,0xE5,0x44,0xB8,0x00,0x15,0xAF,0x8C,
286 0x78,0x8F,0xA8,0x05,0xA7,0x07,0x25,0x9A,0xC8,0x5D,0x90,0x1A,0x41,0x53,0x30,0xD3,0x24,0x33,0x71,0xB4,0x50,0x6E,0xE4,0xEA,0x0D,0x2B,0x6D,0xF5,0x17,0x08,0x74,0x49,
287 0x71,0xC2,0xAC,0xF7,0xDC,0xB2,0x7E,0xCC,0xB6,0x1B,0xB8,0xA9,0x52,0xCF,0x6B,0x51,0xD2,0x4E,0xC9,0x43,0xEE,0x2E,0x92,0x24,0xBB,0x47,0x4D,0x0C,0x3E,0x21,0x53,0x19,
288 0xD4,0x82,0xE2,0xC6,0x93,0x85,0x0A,0xF8,0xFA,0x04,0x07,0xD3,0x1D,0xEC,0x03,0x66,0xFD,0xB1,0xFB,0x8F,0xC5,0xDE,0xE8,0x29,0xDF,0x23,0x09,0x9D,0x7C,0x43,0x3D,0x4D,
289 0x89,0xB9,0x6F,0xB4,0x6B,0x4A,0x51,0xC3,0x94,0xF4,0x7C,0x5E,0x19,0x87,0x79,0xC1,0x80,0x0C,0x45,0x12,0xEC,0x95,0xF3,0x31,0x68,0x42,0xE1,0x06,0x57,0x0E,0xA7,0xFB,
290 0x78,0x96,0x87,0x23,0xA5,0x20,0x7A,0x09,0x3A,0x45,0xE6,0xD9,0x5E,0x6A,0xD6,0xAA,0x29,0x50,0x92,0x4E,0xD0,0xB5,0x91,0xC2,0x9A,0xCF,0x07,0xFE,0xB2,0x15,0xEB,0xE4,
291 0x84,0x40,0x14,0x47,0xFA,0x93,0xB9,0x06,0x69,0xDB,0xBD,0x4E,0xEA,0x52,0x9B,0xDE,0x5B,0x50,0x36,0xAB,0xB3,0x1F,0xD2,0xCD,0x9C,0x13,0x07,0x7E,0x8B,0xED,0x72,0x62,
292 0x74,0x77,0x3B,0x88,0xAC,0x5B,0x6A,0xBC,0xDA,0x99,0xE8,0x24,0x90,0x5A,0xCA,0x8D,0x5C,0x2B,0xF8,0xF1,0xE1,0x1D,0x94,0x11,0xEA,0xCC,0x02,0x09,0x1E,0xA2,0x48,0x67,
293 0x87,0x5A,0x7E,0xC6,0xCC,0xA3,0xFB,0xC5,0x36,0xEB,0x5C,0xE1,0xAF,0x1E,0xBE,0xE7,0xD8,0x8F,0x70,0xAE,0x42,0x05,0xF5,0xCD,0x2D,0xA2,0xB0,0xFD,0xEF,0x65,0x2C,0x22,
294 0xCB,0x8C,0x8B,0xAA,0x3D,0x86,0xE2,0xCD,0xBE,0xC3,0x42,0x38,0xE3,0x9C,0x08,0xB5,0xAE,0xBD,0x54,0x73,0x83,0x70,0x24,0x47,0xCA,0x4C,0x04,0xC4,0xE0,0x1D,0x40,0xED,
295 0xF4,0x2B,0x50,0x8E,0x97,0xB3,0xF0,0xA6,0x76,0xDB,0x49,0x30,0xE5,0xD9,0x71,0x07,0xB2,0xF1,0x0F,0xD6,0x77,0xAA,0x72,0xC0,0xAF,0x66,0xD8,0x40,0xC6,0x08,0x19,0x8C,
296 0xD9,0x8F,0x5A,0x75,0xAC,0xBE,0xC2,0x40,0x5B,0xBD,0x0D,0x1D,0x00,0xAF,0x26,0x5E,0x78,0x43,0xAA,0xC6,0x4F,0xF3,0xD8,0xE2,0x7F,0x0C,0x1E,0x77,0x4D,0x35,0x96,0x23,
297 0x32,0x44,0x03,0x8D,0x92,0xE7,0xFD,0x48,0x07,0xD0,0x58,0xFC,0x6D,0xC9,0x91,0x33,0xF0,0x23,0x45,0xA4,0x29,0xB9,0xF5,0xB0,0x68,0x8F,0x7B,0x59,0x15,0x8E,0xA6,0x66,
298 0x15,0xA0,0x76,0x9B,0x69,0xCB,0x38,0xA5,0xF4,0xB4,0x6B,0xDC,0x1F,0xAB,0xAE,0x12,0x77,0xC0,0x8C,0x4A,0x03,0xB9,0x73,0xD3,0x6D,0x52,0xC5,0xF5,0x6E,0x4E,0x4B,0xA3,
299 0x24,0x02,0x58,0xEE,0x5F,0xF9,0xD6,0xD0,0x1D,0xBC,0xF4,0xB8,0x4F,0xFD,0x4B,0x2D,0x34,0x77,0x46,0xE5,0xD4,0x33,0x7B,0x9C,0x35,0xCD,0xB0,0x5D,0x06,0x39,0x99,0xEB,
300 0x0C,0xD0,0x0F,0xF7,0x92,0xB5,0x58,0x5B,0x5E,0x79,0x12,0xF4,0x05,0xF6,0x21,0x07,0x0B,0x49,0x1A,0xFB,0xD4,0x98,0xC4,0xEF,0x7A,0xD6,0xCA,0xA1,0xDA,0xB3,0x51,0x00,
301 0x76,0xEC,0x08,0x48,0x40,0x35,0xD7,0x94,0xBE,0xF5,0x7B,0xA4,0x20,0x81,0x5F,0x82,0xF3,0x6F,0x96,0x24,0x98,0xB6,0x49,0x18,0xC8,0xC5,0x8C,0xD2,0x38,0x7F,0xC4,0xF6,
302 0xAA,0x87,0xDC,0x73,0x5B,0xA1,0xAF,0xE5,0x3D,0x37,0x6B,0x85,0xED,0x38,0x62,0x7D,0x57,0xBD,0xCF,0xB5,0x1B,0xA8,0xBB,0x32,0x33,0xD3,0x34,0x5A,0xC1,0x5D,0xFB,0x28,
303 0x6E,0xE1,0x67,0x51,0xBB,0x31,0x92,0x83,0xAC,0xAA,0x72,0x52,0xFD,0x13,0x4F,0x73,0xD3,0xF0,0x5E,0xFC,0xBA,0xB1,0x3C,0x7B,0x08,0x76,0x03,0x38,0x1E,0xD1,0xCC,0x33,
304 0xA3,0x1E,0xFC,0xE0,0x82,0x30,0x27,0x93,0x71,0x35,0x75,0x77,0xBA,0x78,0x10,0x33,0xCD,0xAB,0xCF,0x8E,0xAD,0xF9,0x32,0xC9,0x15,0x9F,0xD6,0x6D,0xA8,0xAE,0xB1,0x3F,
305 0x90,0xEB,0xD4,0xF9,0x31,0x81,0xA3,0x53,0x99,0x4B,0x3C,0x93,0x3B,0xFE,0x55,0xFF,0x25,0x9F,0xCC,0x07,0xC5,0x2C,0x14,0xA7,0xA4,0x1E,0x6C,0xB6,0x91,0x2A,0xE0,0x3E,
306 0x7F,0x39,0x0A,0xD9,0x24,0x3C,0x01,0xA0,0x30,0x99,0x8E,0xB8,0x1D,0xF9,0xA7,0x78,0x86,0x95,0x35,0x0E,0x21,0xDA,0x7A,0x7B,0xAD,0x9F,0x4E,0xF6,0x63,0x5B,0x96,0xBB,
307 0x87,0x36,0x3F,0xA7,0x1A,0x66,0x91,0xCD,0xB0,0x3B,0xC0,0x4F,0x54,0xD2,0x5F,0xBB,0x38,0x89,0x1C,0x79,0x7E,0xA2,0x02,0xE4,0x80,0x84,0x1E,0x33,0xAB,0x74,0xFA,0xBE,
308 0x31,0x46,0x2E,0xC5,0x15,0xB9,0x12,0xE9,0xD3,0x73,0x43,0xEA,0x74,0x11,0xA7,0xC0,0xD5,0xD8,0x39,0x08,0x9F,0x4F,0xC7,0x71,0x25,0x09,0x51,0x65,0xD6,0xA8,0x02,0x1F
309 };
310
311 return random_noise[ (random_noise[p[1] & 1023] + p[0]) & 1023 ] & umod;
312 }
313
314 // MESH
315 // ===========================================================================================================
316
317 static void init_mesh( struct mesh *m, float const *tris, u32 length )
318 {
319 m->elements = length/3;
320 glGenVertexArrays( 1, &m->vao );
321 glGenBuffers( 1, &m->vbo );
322
323 glBindVertexArray( m->vao );
324 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
325 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
326
327 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
328 glEnableVertexAttribArray( 0 );
329
330 VG_CHECK_GL();
331 }
332
333 static void free_mesh( struct mesh *m )
334 {
335 glDeleteVertexArrays( 1, &m->vao );
336 glDeleteBuffers( 1, &m->vbo );
337 }
338
339 static void draw_mesh( int const start, int const count )
340 {
341 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
342 }
343
344 static void use_mesh( struct mesh *m )
345 {
346 glBindVertexArray( m->vao );
347 }
348
349 static struct cell_button *get_wbutton( enum e_world_button btn )
350 {
351 return &world.buttons[ btn ];
352 }
353
354 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
355
356 // WORLD/MAP
357 // ===========================================================================================================
358
359 static void map_free(void)
360 {
361 arrfree( world.data );
362 arrfree( world.io );
363
364 world.w = 0;
365 world.h = 0;
366 world.data = NULL;
367 world.io = NULL;
368 world.score = 0;
369 world.time = 0;
370 world.completed = 0;
371 world.max_runs = 0;
372 world.initialzed = 0;
373 }
374
375 static void io_reset(void)
376 {
377 for( int i = 0; i < arrlen( world.io ); i ++ )
378 {
379 struct cell_terminal *term = &world.io[i];
380
381 for( int j = 0; j < term->run_count; j ++ )
382 term->runs[j].recv_count = 0;
383 }
384 }
385
386 static struct cell *pcell( v2i pos )
387 {
388 return &world.data[ pos[1]*world.w + pos[0] ];
389 }
390
391 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
392 {
393 v2i full_start = { 1,1 };
394 v2i full_end = { world.w-1, world.h-1 };
395
396 if( !start || !end )
397 {
398 start = full_start;
399 end = full_end;
400 }
401
402 // Texture data
403 u8 info_buffer[64*64*4];
404 u32 pixel_id = 0;
405
406 int px0 = vg_max( start[0], full_start[0] ),
407 px1 = vg_min( end[0], full_end[0] ),
408 py0 = vg_max( start[1], full_start[1] ),
409 py1 = vg_min( end[1], full_end[1] );
410
411 for( int y = py0; y < py1; y ++ )
412 {
413 for( int x = px0; x < px1; x ++ )
414 {
415 struct cell *cell = pcell((v2i){x,y});
416
417 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
418
419 u8 height = 0;
420 u8 config = 0x00;
421
422 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
423 {
424 for( int i = 0; i < vg_list_size( dirs ); i ++ )
425 {
426 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
427 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
428 config |= 0x1 << i;
429 }
430
431 height = 128;
432 }
433 else
434 {
435 if( cell->state & FLAG_WALL )
436 height = 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F );
437
438 config = 0xF;
439 }
440
441 pcell((v2i){x,y})->config = config;
442
443 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
444 info_px[0] = height;
445 info_px[1] = cell->state & FLAG_WALL? 0: 255;
446 info_px[2] = 0;
447 info_px[3] = 0;
448
449 if(
450 (
451 ((cell->state & FLAG_IS_TRIGGER) && (cell->config == 0xF || cell->config == k_cell_type_split)) ||
452 ((cell->state & FLAG_TARGETED) && (cell->config != k_cell_type_split))
453 ) && update_texbuffer
454 ){
455 cell->state &= ~(FLAG_TARGETED|FLAG_IS_TRIGGER);
456 for( u32 i = 0; i < 2; i ++ )
457 {
458 if( cell->links[i] )
459 {
460 struct cell *other_ptr = &world.data[ cell->links[i] ];
461 other_ptr->links[ i ] = 0;
462 other_ptr->state &= ~FLAG_IS_TRIGGER;
463
464 if( other_ptr->links[ i ^ 0x1 ] == 0 )
465 other_ptr->state &= ~FLAG_TARGETED;
466 }
467 }
468
469 cell->links[0] = 0;
470 cell->links[1] = 0;
471 }
472 }
473 }
474
475 if( update_texbuffer )
476 {
477 glBindTexture( GL_TEXTURE_2D, world.background_data );
478 glTexSubImage2D( GL_TEXTURE_2D, 0, px0 + 16, py0 + 16, px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
479 }
480 }
481
482 static int map_load( const char *str, const char *name )
483 {
484 //TODO: It may be worthwhile, at this point, to switch to binary encoding for save data
485
486 map_free();
487
488 char const *c = str;
489
490 // Scan for width
491 for(;; world.w ++)
492 {
493 if( c[world.w] == ';' )
494 break;
495 else if( !c[world.w] )
496 {
497 vg_error( "Unexpected EOF when parsing level\n" );
498 return 0;
499 }
500 }
501
502 struct cell *row = arraddnptr( world.data, world.w );
503 int cx = 0;
504 int reg_start = 0, reg_end = 0;
505
506 u32 *links_to_make = NULL;
507 int links_satisfied = 0;
508
509 char link_id_buffer[32];
510 int link_id_n = 0;
511
512 for(;;)
513 {
514 if( !*c )
515 break;
516
517 if( *c == '\r' ) { c ++; continue; } // fuck off windows
518
519 if( *c == ';' )
520 {
521 c ++;
522
523 if( *c == '\r' ) c ++;
524
525 // Parse attribs
526 if( *c != '\n' )
527 {
528 while( *c )
529 {
530 if( *c == '\r' ) { c ++; continue; }
531
532 if( reg_start < reg_end )
533 {
534 struct cell_terminal *terminal = &world.io[ reg_start ];
535 struct terminal_run *run = &terminal->runs[ terminal->run_count-1 ];
536
537 if( (*c >= 'a' && *c <= 'z') || *c == ' ' )
538 {
539 run->conditions[ run->condition_count ++ ] = *c;
540 }
541 else
542 {
543 if( *c == ',' || *c == '\n' )
544 {
545 reg_start ++;
546
547 if( *c == '\n' )
548 break;
549 }
550 else if( *c == ':' )
551 {
552 terminal->runs[ terminal->run_count ].condition_count = 0;
553 terminal->run_count ++;
554 world.max_runs = vg_max( world.max_runs, terminal->run_count );
555 }
556 else
557 {
558 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
559 goto IL_REG_ERROR;
560 }
561 }
562 }
563 else
564 {
565 if( links_satisfied < arrlen( links_to_make ) )
566 {
567 struct cell *target = &world.data[ links_to_make[ links_satisfied ] ];
568
569 if( (((u32)*c >= (u32)'0') && ((u32)*c <= (u32)'9')) || *c == '-' )
570 {
571 if( link_id_n >= vg_list_size( link_id_buffer )-1 )
572 {
573 vg_error( "Number was way too long to be parsed (row: %u)\n", world.h );
574 goto IL_REG_ERROR;
575 }
576
577 link_id_buffer[ link_id_n ++ ] = *c;
578 }
579 else if( *c == ',' || *c == '\n' )
580 {
581 link_id_buffer[ link_id_n ] = 0x00;
582 int value = atoi( link_id_buffer );
583
584 target->links[value >= 0? 1:0] = abs(value);
585 links_satisfied ++;
586 link_id_n = 0;
587
588 if( *c == '\n' )
589 break;
590 }
591 else
592 {
593 vg_error( "Invalid character '%c' (row: %u)\n", *c, world.h );
594 goto IL_REG_ERROR;
595 }
596 }
597 else
598 {
599 vg_error( "Too many values to assign (row: %u)\n", world.h );
600 goto IL_REG_ERROR;
601 }
602 }
603
604 c ++;
605 }
606 }
607
608 // Registry length-error checks
609 if( reg_start != reg_end )
610 {
611 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
612 goto IL_REG_ERROR;
613 }
614
615 if( links_satisfied != arrlen( links_to_make ) )
616 {
617 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n", world.h, links_satisfied, arrlen( links_to_make ) );
618 goto IL_REG_ERROR;
619 }
620
621 if( cx != world.w )
622 {
623 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
624 goto IL_REG_ERROR;
625 }
626
627 row = arraddnptr( world.data, world.w );
628 cx = 0;
629 world.h ++;
630 reg_end = reg_start = arrlen( world.io );
631
632 arrsetlen( links_to_make, 0 );
633 links_satisfied = 0;
634 }
635 else
636 {
637 if( cx == world.w )
638 {
639 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
640 goto IL_REG_ERROR;
641 }
642
643 // Tile initialization
644 // row[ cx ] .. etc
645 struct cell *cell = &row[ cx ];
646 cell->config = 0xF;
647
648 if( *c == '+' || *c == '-' )
649 {
650 struct cell_terminal *term = arraddnptr( world.io, 1 );
651 term->pos[0] = cx;
652 term->pos[1] = world.h;
653
654 term->run_count = 1;
655 term->runs[0].condition_count = 0;
656
657 cell->state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
658 reg_end ++;
659 }
660 else if( *c == '#' ) cell->state = FLAG_WALL;
661 else if( ((u32)*c >= (u32)'A') && ((u32)*c <= (u32)'A'+0xf) )
662 {
663 // Canal flag bits (4bit/16 value):
664 // 0: Canal present
665 // 1: Is trigger
666 // 2: Reserved
667 // 3: Reserved
668
669 cell->state = ((u32)*c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
670
671 if( cell->state & FLAG_IS_TRIGGER )
672 arrpush( links_to_make, cx + world.h*world.w );
673
674 cell->links[0] = 0;
675 cell->links[1] = 0;
676 world.score ++;
677 }
678 else cell->state = 0x00;
679
680 cx ++;
681 }
682
683 c ++;
684 }
685
686 // Update data texture to fill out the background
687 {
688 u8 info_buffer[64*64*4];
689 for( int x = 0; x < 64; x ++ )
690 {
691 for( int y = 0; y < 64; y ++ )
692 {
693 u8 *px = &info_buffer[((x*64)+y)*4];
694
695 px[0] = 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F );
696 px[1] = 0;
697 px[2] = 0;
698 px[3] = 0;
699 }
700 }
701
702 // Level selection area
703
704 // Beginner
705 for( int y = 16+2; y < 16+2+4; y ++ )
706 {
707 u8 *px = &info_buffer[((y*64)+16-1)*4];
708 px[0] = 0x10;
709 }
710
711 // Intermediate
712 for( int y = 16+2; y < 16+2+6; y ++ )
713 {
714 for( int x = 0; x < 3; x ++ )
715 {
716 u8 *px = &info_buffer[((y*64)+16-5+x)*4];
717 px[0] = 0x10;
718 }
719 }
720
721 // Expert
722 for( int x = 1; x < 5; x ++ )
723 {
724 u8 *px = &info_buffer[((16*64)+16-5+x)*4];
725 px[0] = 0x10;
726 }
727
728 info_buffer[(((16+world.h-3)*64)+world.w+16-1)*4] = 0x30;
729 info_buffer[(((16+world.h-2)*64)+world.w+16-1)*4] = 0x30;
730
731 // Random walks.. kinda
732 for( int i = 0; i < arrlen(world.io); i ++ )
733 {
734 struct cell_terminal *term = &world.io[ i ];
735
736 v2i turtle;
737 v2i turtle_dir;
738 int original_y;
739
740 turtle[0] = 16+term->pos[0];
741 turtle[1] = 16+term->pos[1];
742
743 turtle_dir[0] = 0;
744 turtle_dir[1] = pcell(term->pos)->state & FLAG_INPUT? 1: -1;
745 original_y = turtle_dir[1];
746
747 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
748 v2i_add( turtle_dir, turtle, turtle );
749
750 for( int i = 0; i < 100; i ++ )
751 {
752 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
753
754 v2i_add( turtle_dir, turtle, turtle );
755
756 if( turtle[0] == 0 ) break;
757 if( turtle[0] == 63 ) break;
758 if( turtle[1] == 0 ) break;
759 if( turtle[1] == 63 ) break;
760
761 int random_value = hash21i( turtle, 0xFF );
762 if( random_value > 255-40 && !turtle_dir[0] )
763 {
764 turtle_dir[0] = -1;
765 turtle_dir[1] = 0;
766 }
767 else if( random_value > 255-80 && !turtle_dir[0] )
768 {
769 turtle_dir[0] = 1;
770 turtle_dir[1] = 0;
771 }
772 else if( random_value > 255-100 )
773 {
774 turtle_dir[0] = 0;
775 turtle_dir[1] = original_y;
776 }
777 }
778 }
779
780 glBindTexture( GL_TEXTURE_2D, world.background_data );
781 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
782 }
783
784 arrfree( links_to_make );
785
786 map_reclassify( NULL, NULL, 1 );
787
788 // Validate links
789 for( int i = 0; i < world.h*world.w; i ++ )
790 {
791 struct cell *src = &world.data[i];
792 if( src->state & FLAG_IS_TRIGGER )
793 {
794 int link_id = src->links[0]?0:1;
795 if( src->links[link_id] <= world.h*world.w )
796 {
797 struct cell *target = &world.data[ src->links[link_id] ];
798 if( (target->state & FLAG_CANAL) && (target->config == k_cell_type_split) )
799 {
800 if( target->links[ link_id ] )
801 {
802 vg_error( "Link target was already targeted\n" );
803 goto IL_REG_ERROR;
804 }
805 else
806 {
807 // Valid link
808 target->links[ link_id ] = i;
809 target->state |= FLAG_TARGETED;
810 }
811 }
812 else
813 {
814 vg_error( "Link target was invalid\n" );
815 goto IL_REG_ERROR;
816 }
817 }
818 else
819 {
820 vg_error( "Link target out of bounds\n" );
821 goto IL_REG_ERROR;
822 }
823 }
824 }
825
826 vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
827
828 io_reset();
829
830 strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
831 world.initialzed = 1;
832 return 1;
833
834 IL_REG_ERROR:
835 arrfree( links_to_make );
836 map_free();
837 return 0;
838 }
839
840 static void map_serialize( FILE *stream )
841 {
842 for( int y = 0; y < world.h; y ++ )
843 {
844 for( int x = 0; x < world.w; x ++ )
845 {
846 struct cell *cell = pcell( (v2i){ x, y } );
847
848 if( cell->state & FLAG_WALL ) fputc( '#', stream );
849 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
850 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
851 else if( cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1) )
852 {
853 fputc( (cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)) + (u32)'A', stream );
854 }
855 else fputc( ' ', stream );
856 }
857
858 fputc( ';', stream );
859
860 int terminal_write_count = 0;
861
862 for( int x = 0; x < world.w; x ++ )
863 {
864 for( int i = 0; i < arrlen( world.io ); i ++ )
865 {
866 struct cell_terminal *term = &world.io[i];
867 if( v2i_eq( term->pos, (v2i){x,y} ) )
868 {
869 if( terminal_write_count )
870 fputc( ',', stream );
871 terminal_write_count ++;
872
873 for( int j = 0; j < term->run_count; j ++ )
874 {
875 struct terminal_run *run = &term->runs[j];
876
877 for( int k = 0; k < run->condition_count; k ++ )
878 fputc( run->conditions[k], stream );
879
880 if( j < term->run_count-1 )
881 fputc( ':', stream );
882 }
883 }
884 }
885 }
886
887 for( int x = 0; x < world.w; x ++ )
888 {
889 struct cell *cell = pcell( (v2i){ x,y } );
890 if( cell->state & FLAG_IS_TRIGGER )
891 {
892 if( terminal_write_count )
893 fputc( ',', stream );
894 terminal_write_count ++;
895
896 fprintf( stream, "%d", cell->links[0]? -cell->links[0]: cell->links[1] );
897 }
898 }
899
900 fputc( '\n', stream );
901 }
902 }
903
904 // CAREER STATE
905 // ===========================================================================================================
906
907 #pragma pack(push,1)
908 struct dcareer_state
909 {
910 u32 version;
911 i32 in_map;
912
913 u32 reserved[14];
914
915 struct dlevel_state
916 {
917 i32 score;
918 i32 unlocked;
919 i32 reserved[2];
920 }
921 levels[ NUM_CAMPAIGN_LEVELS ];
922 };
923 #pragma pack(pop)
924
925 static int career_load_success = 0;
926
927 static void career_serialize(void)
928 {
929 if( !career_load_success )
930 return;
931
932 struct dcareer_state encoded;
933 encoded.version = 2;
934 encoded.in_map = world.pCmpLevel? world.pCmpLevel->serial_id: -1;
935
936 memset( encoded.reserved, 0, sizeof( encoded.reserved ) );
937
938 for( int i = 0; i < vg_list_size( career_serializable ); i ++ )
939 {
940 struct serializable_set *set = &career_serializable[i];
941
942 for( int j = 0; j < set->count; j ++ )
943 {
944 struct cmp_level *lvl = &set->pack[j];
945 struct dlevel_state *dest = &encoded.levels[lvl->serial_id];
946
947 dest->score = lvl->completed_score;
948 dest->unlocked = lvl->unlocked;
949 dest->reserved[0] = 0;
950 dest->reserved[1] = 0;
951 }
952 }
953
954 vg_asset_write( "sav/game.sv2", &encoded, sizeof( struct dcareer_state ) );
955 }
956
957 static void career_unlock_level( struct cmp_level *lvl );
958 static void career_unlock_level( struct cmp_level *lvl )
959 {
960 lvl->unlocked = 1;
961
962 if( lvl->linked )
963 career_unlock_level( lvl->linked );
964 }
965
966 static void career_pass_level( struct cmp_level *lvl, int score, int upload )
967 {
968 if( score > 0 )
969 {
970 if( score < lvl->completed_score || lvl->completed_score == 0 )
971 {
972 if( !lvl->is_tutorial && upload )
973 leaderboard_set_score( lvl, score );
974
975 lvl->completed_score = score;
976 }
977
978 if( lvl->unlock ) career_unlock_level( lvl->unlock );
979 }
980 }
981
982 static void career_reset_level( struct cmp_level *lvl )
983 {
984 lvl->unlocked = 0;
985 lvl->completed_score = 0;
986 }
987
988 static void career_load(void)
989 {
990 i64 sz;
991 struct dcareer_state encoded;
992
993 // Blank save state
994 memset( (void*)&encoded, 0, sizeof( struct dcareer_state ) );
995 encoded.in_map = -1;
996 encoded.levels[0].unlocked = 1;
997
998 // Load and copy data into encoded
999 void *cr = vg_asset_read_s( "sav/game.sv2", &sz );
1000
1001 if( cr )
1002 {
1003 if( sz > sizeof( struct dcareer_state ) )
1004 vg_warn( "This save file is too big! Some levels will be lost\n" );
1005
1006 if( sz <= offsetof( struct dcareer_state, levels ) )
1007 {
1008 vg_warn( "This save file is too small to have a header. Creating a blank one\n" );
1009 free( cr );
1010 }
1011
1012 memcpy( (void*)&encoded, cr, VG_MIN( sizeof( struct dcareer_state ), sz ) );
1013 free( cr );
1014 }
1015 else
1016 vg_info( "No save file... Using blank one\n" );
1017
1018 // Reset memory
1019 for( int i = 0; i < vg_list_size( career_serializable ); i ++ )
1020 {
1021 struct serializable_set *set = &career_serializable[i];
1022
1023 for( int j = 0; j < set->count; j ++ )
1024 career_reset_level( &set->pack[j] );
1025 }
1026
1027 // Header information
1028 // =================================
1029
1030 // Decode everything from dstate
1031 for( int i = 0; i < vg_list_size( career_serializable ); i ++ )
1032 {
1033 struct serializable_set *set = &career_serializable[i];
1034
1035 for( int j = 0; j < set->count; j ++ )
1036 {
1037 struct cmp_level *lvl = &set->pack[j];
1038 struct dlevel_state *src = &encoded.levels[lvl->serial_id];
1039
1040 if( src->unlocked ) career_unlock_level( lvl );
1041 if( src->score ) lvl->completed_score = src->score;
1042
1043 // ...
1044 if( lvl->serial_id == encoded.in_map )
1045 {
1046 if( console_changelevel( 1, &lvl->map_name ) )
1047 world.pCmpLevel = lvl;
1048 }
1049 }
1050 }
1051
1052 career_load_success = 1;
1053 }
1054
1055 // MAIN GAMEPLAY
1056 // ===========================================================================================================
1057 static int is_simulation_running(void)
1058 {
1059 return world.buttons[ k_world_button_sim ].pressed;
1060 }
1061
1062 static void clear_animation_flags(void)
1063 {
1064 for( int i = 0; i < world.w*world.h; i ++ )
1065 world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
1066 }
1067
1068 static void simulation_stop(void)
1069 {
1070 world.buttons[ k_world_button_sim ].pressed = 0;
1071 world.buttons[ k_world_button_pause ].pressed = 0;
1072
1073 world.num_fishes = 0;
1074 world.sim_frame = 0;
1075 world.sim_run = 0;
1076 world.frame_lerp = 0.0f;
1077
1078 io_reset();
1079
1080 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
1081
1082 clear_animation_flags();
1083
1084 vg_info( "Stopping simulation!\n" );
1085 }
1086
1087 static void simulation_start(void)
1088 {
1089 vg_success( "Starting simulation!\n" );
1090
1091 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1092
1093 world.num_fishes = 0;
1094 world.sim_frame = 0;
1095 world.sim_run = 0;
1096
1097 world.sim_delta_speed = world.buttons[ k_world_button_speedy ].pressed? 10.0f: 2.5f;
1098 world.sim_delta_ref = vg_time;
1099 world.sim_internal_ref = 0.0f;
1100 world.sim_internal_time = 0.0f;
1101 world.pause_offset_target = 0.0f;
1102
1103 world.sim_target = 0;
1104
1105 clear_animation_flags();
1106
1107 io_reset();
1108
1109 if( world.pCmpLevel )
1110 {
1111 world.pCmpLevel->completed_score = 0;
1112 }
1113 }
1114
1115 static int world_check_pos_ok( v2i co )
1116 {
1117 return (co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2)? 0: 1;
1118 }
1119
1120 static int cell_interactive( v2i co )
1121 {
1122 // Bounds check
1123 if( !world_check_pos_ok( co ) )
1124 return 0;
1125
1126 // Flags check
1127 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
1128 return 0;
1129
1130 // List of 3x3 configurations that we do not allow
1131 static u32 invalid_src[][9] =
1132 {
1133 { 0,1,0,
1134 1,1,1,
1135 0,1,0
1136 },
1137 { 0,0,0,
1138 0,1,1,
1139 0,1,1
1140 },
1141 { 0,0,0,
1142 1,1,0,
1143 1,1,0
1144 },
1145 { 0,1,1,
1146 0,1,1,
1147 0,0,0
1148 },
1149 { 1,1,0,
1150 1,1,0,
1151 0,0,0
1152 },
1153 { 0,1,0,
1154 0,1,1,
1155 0,1,0
1156 },
1157 { 0,1,0,
1158 1,1,0,
1159 0,1,0
1160 }
1161 };
1162
1163 // Statically compile invalid configurations into bitmasks
1164 static u32 invalid[ vg_list_size(invalid_src) ];
1165
1166 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
1167 {
1168 u32 comped = 0x00;
1169
1170 for( int j = 0; j < 3; j ++ )
1171 for( int k = 0; k < 3; k ++ )
1172 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
1173
1174 invalid[i] = comped;
1175 }
1176
1177 // Extract 5x5 grid surrounding tile
1178 u32 blob = 0x1000;
1179 for( int y = co[1]-2; y < co[1]+3; y ++ )
1180 for( int x = co[0]-2; x < co[0]+3; x ++ )
1181 {
1182 struct cell *cell = pcell((v2i){x,y});
1183
1184 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
1185 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1186 }
1187
1188 // Run filter over center 3x3 grid to check for invalid configurations
1189 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1190 for( int i = 0; i < vg_list_size(kernel); i ++ )
1191 {
1192 if( blob & (0x1 << (6+kernel[i])) )
1193 {
1194 u32 window = blob >> kernel[i];
1195
1196 for( int j = 0; j < vg_list_size(invalid); j ++ )
1197 if((window & invalid[j]) == invalid[j])
1198 return 0;
1199 }
1200 }
1201
1202 return 1;
1203 }
1204
1205 void vg_update(void)
1206 {
1207 // Camera
1208 // ========================================================================================================
1209
1210 float r1 = (float)vg_window_y / (float)vg_window_x,
1211 r2 = (float)world.h / (float)world.w,
1212 size;
1213
1214 static float size_current = 2.0f;
1215 static v3f origin_current = { 0.0f, 0.0f, 0.0f };
1216
1217 size = ( r2 < r1? (float)(world.w+5) * 0.5f: ((float)(world.h+5) * 0.5f) / r1 ) + 0.5f;
1218
1219 v3f origin;
1220 origin[0] = floorf( -0.5f * ((float)world.w-4.5f) );
1221 origin[1] = floorf( -0.5f * world.h );
1222 origin[2] = 0.0f;
1223
1224 // Lerp towards target
1225 size_current = vg_lerpf( size_current, size, vg_time_delta * 6.0f );
1226 v2_lerp( origin_current, origin, vg_time_delta * 6.0f, origin_current );
1227
1228 m3x3_projection( m_projection, -size_current, size_current, -size_current*r1, size_current*r1 );
1229 m3x3_identity( m_view );
1230 m3x3_translate( m_view, origin_current );
1231 m3x3_mul( m_projection, m_view, vg_pv );
1232 vg_projection_update();
1233
1234 // Mouse input
1235 // ========================================================================================================
1236 v2_copy( vg_mouse_ws, world.tile_pos );
1237
1238 world.tile_x = floorf( world.tile_pos[0] );
1239 world.tile_y = floorf( world.tile_pos[1] );
1240
1241 // Tilemap
1242 // ========================================================================================================
1243 if( !is_simulation_running() && !gui_want_mouse() )
1244 {
1245 v2_copy( vg_mouse_ws, world.drag_to_co );
1246
1247 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1248 {
1249 world.selected = world.tile_y * world.w + world.tile_x;
1250
1251 static u32 modify_state = 0;
1252
1253 struct cell *cell_ptr = &world.data[world.selected];
1254
1255 if( vg_get_button_down("primary") )
1256 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1257
1258 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1259 {
1260 cell_ptr->state &= ~FLAG_CANAL;
1261 cell_ptr->state |= modify_state;
1262
1263 if( cell_ptr->state & FLAG_CANAL )
1264 {
1265 cell_ptr->links[0] = 0;
1266 cell_ptr->links[1] = 0;
1267
1268 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1269 world.score ++;
1270 }
1271 else
1272 {
1273 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1274 world.score --;
1275 }
1276
1277 map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 },
1278 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1279 }
1280
1281 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1282 {
1283 world.id_drag_from = world.selected;
1284
1285 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1286 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1287 }
1288
1289 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1290
1291 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1292 {
1293 u32 link_id = cell_ptr->links[ 0 ]? 0: 1;
1294
1295 // break existing connection off
1296 if( cell_ptr->links[ link_id ] )
1297 {
1298 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1299
1300 if( !current_connection->links[ link_id ^ 0x1 ] )
1301 current_connection->state &= ~FLAG_TARGETED;
1302
1303 current_connection->links[ link_id ] = 0;
1304 cell_ptr->links[ link_id ] = 0;
1305 }
1306
1307 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1308 world.id_drag_from = 0;
1309 }
1310
1311 else if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
1312 {
1313 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1314 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1315
1316 if( vg_get_button_up("secondary") )
1317 {
1318 struct cell *drag_ptr = &world.data[world.id_drag_from];
1319 u32 link_id = local_x > 0.5f? 1: 0;
1320
1321 // Cleanup existing connections
1322 if( cell_ptr->links[ link_id ] )
1323 {
1324 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1325
1326 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1327 current_connection->state &= ~FLAG_IS_TRIGGER;
1328 current_connection->links[ link_id ] = 0;
1329 }
1330
1331 if( drag_ptr->links[ link_id ^ 0x1 ] )
1332 {
1333 vg_warn( "Destroying alternate link %u (%hu)\n", link_id ^ 0x1, drag_ptr->links[ link_id ^ 0x1 ] );
1334
1335 struct cell *current_connection = &world.data[ drag_ptr->links[ link_id ^ 0x1 ]];
1336 if( !current_connection->links[ link_id ] )
1337 current_connection->state &= ~FLAG_TARGETED;
1338
1339 current_connection->links[ link_id ^ 0x1 ] = 0;
1340 drag_ptr->links[ link_id ^ 0x1 ] = 0;
1341 }
1342
1343 // Create the new connection
1344 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1345
1346 cell_ptr->links[ link_id ] = world.id_drag_from;
1347 drag_ptr->links[ link_id ] = world.selected;
1348
1349 cell_ptr->state |= FLAG_TARGETED;
1350 drag_ptr->state |= FLAG_IS_TRIGGER;
1351 world.id_drag_from = 0;
1352 }
1353 }
1354 }
1355 else
1356 {
1357 world.selected = -1;
1358 }
1359
1360 if( !(vg_get_button("secondary") && world.id_drag_from) )
1361 world.id_drag_from = 0;
1362 }
1363 else
1364 {
1365 world.selected = -1;
1366 world.id_drag_from = 0;
1367 }
1368
1369 // Marble state updates
1370 // ========================================================================================================
1371 if( is_simulation_running() )
1372 {
1373 float old_time = world.sim_internal_time;
1374
1375 if( !world.buttons[ k_world_button_pause ].pressed )
1376 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1377 else
1378 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1379 world.sim_internal_delta = world.sim_internal_time-old_time;
1380
1381 world.sim_target = (int)floorf(world.sim_internal_time);
1382
1383 int success_this_frame = 0;
1384 int failure_this_frame = 0;
1385
1386 while( world.sim_frame < world.sim_target )
1387 {
1388 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1389
1390 // Update splitter deltas
1391 for( int i = 0; i < world.h*world.w; i ++ )
1392 {
1393 struct cell *cell = &world.data[i];
1394 if( cell->config == k_cell_type_split )
1395 {
1396 cell->state &= ~FLAG_FLIP_ROTATING;
1397 }
1398 if( cell->state & FLAG_IS_TRIGGER )
1399 cell->state &= ~FLAG_TRIGGERED;
1400 }
1401
1402 int alive_count = 0;
1403
1404 // Update fish positions
1405 for( int i = 0; i < world.num_fishes; i ++ )
1406 {
1407 struct fish *fish = &world.fishes[i];
1408
1409 if( fish->state == k_fish_state_soon_dead )
1410 fish->state = k_fish_state_dead;
1411
1412 if( fish->state == k_fish_state_soon_alive )
1413 fish->state = k_fish_state_alive;
1414
1415 if( fish->state < k_fish_state_alive )
1416 continue;
1417
1418 struct cell *cell_current = pcell( fish->pos );
1419
1420 if( fish->state == k_fish_state_alive )
1421 {
1422 // Apply to output
1423 if( cell_current->state & FLAG_OUTPUT )
1424 {
1425 for( int j = 0; j < arrlen( world.io ); j ++ )
1426 {
1427 struct cell_terminal *term = &world.io[j];
1428
1429 if( v2i_eq( term->pos, fish->pos ) )
1430 {
1431 struct terminal_run *run = &term->runs[ world.sim_run ];
1432 if( run->recv_count < vg_list_size( run->recieved ) )
1433 {
1434 if( fish->payload == run->conditions[ run->recv_count ] )
1435 success_this_frame = 1;
1436 else
1437 failure_this_frame = 1;
1438
1439 run->recieved[ run->recv_count ++ ] = fish->payload;
1440 }
1441 else
1442 failure_this_frame = 1;
1443
1444 break;
1445 }
1446 }
1447
1448 fish->state = k_fish_state_dead;
1449 fish->death_time = -1000.0f;
1450 continue;
1451 }
1452
1453
1454 if( cell_current->config == k_cell_type_merge )
1455 {
1456 // Can only move up
1457 fish->dir[0] = 0;
1458 fish->dir[1] = -1;
1459 fish->flow_reversed = 0;
1460 }
1461 else
1462 {
1463 if( cell_current->config == k_cell_type_split )
1464 {
1465 // Flip flop L/R
1466 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
1467 fish->dir[1] = 0;
1468
1469 if( !(cell_current->state & FLAG_TARGETED) )
1470 cell_current->state ^= FLAG_FLIP_FLOP;
1471 }
1472 else
1473 {
1474 // Apply cell out-flow
1475 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
1476
1477 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
1478 }
1479
1480 v2i pos_next;
1481 v2i_add( fish->pos, fish->dir, pos_next );
1482
1483 struct cell *cell_next = pcell( pos_next );
1484
1485 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
1486 {
1487 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
1488
1489 if( cell_next->config == k_cell_type_merge )
1490 {
1491 if( fish->dir[0] == 0 )
1492 {
1493 fish->state = k_fish_state_dead;
1494 fish->death_time = world.sim_internal_time;
1495 }
1496 else
1497 fish->flow_reversed = 0;
1498 }
1499 else
1500 {
1501 if( cell_next->config == k_cell_type_split )
1502 {
1503 if( fish->dir[0] == 0 )
1504 {
1505 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
1506 cell_next->state |= FLAG_FLIP_ROTATING;
1507
1508 fish->flow_reversed = 0;
1509 }
1510 else
1511 {
1512 fish->state = k_fish_state_dead;
1513 fish->death_time = world.sim_internal_time;
1514 }
1515 }
1516 else
1517 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
1518 fish->dir[1] != -desc->start[1] )? 1: 0;
1519 }
1520 }
1521 else
1522 {
1523 if( world_check_pos_ok( fish->pos ) )
1524 fish->state = k_fish_state_bg;
1525 else
1526 {
1527 fish->state = k_fish_state_dead;
1528 fish->death_time = world.sim_internal_time;
1529 }
1530 }
1531 }
1532
1533 //v2i_add( fish->pos, fish->dir, fish->pos );
1534 }
1535 else if( fish->state == k_fish_state_bg )
1536 {
1537 v2i_add( fish->pos, fish->dir, fish->pos );
1538
1539 if( !world_check_pos_ok( fish->pos ) )
1540 {
1541 fish->state = k_fish_state_dead;
1542 fish->death_time = -1000.0f;
1543 }
1544 else
1545 {
1546 struct cell *cell_entry = pcell( fish->pos );
1547
1548 if( cell_entry->state & FLAG_CANAL )
1549 {
1550 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
1551 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
1552 {
1553 sw_set_achievement( "CAN_DO_THAT" );
1554
1555 fish->state = k_fish_state_soon_alive;
1556
1557 fish->dir[0] = 0;
1558 fish->dir[1] = 0;
1559 fish->flow_reversed = 1;
1560
1561 switch( cell_entry->config )
1562 {
1563 case k_cell_type_con_r: fish->dir[0] = 1; break;
1564 case k_cell_type_con_l: fish->dir[0] = -1; break;
1565 case k_cell_type_con_u: fish->dir[1] = 1; break;
1566 case k_cell_type_con_d: fish->dir[1] = -1; break;
1567 }
1568 }
1569 }
1570 }
1571 }
1572 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
1573
1574 if( fish->state >= k_fish_state_alive )
1575 alive_count ++;
1576 }
1577
1578 // Second pass (triggers)
1579 for( int i = 0; i < world.num_fishes; i ++ )
1580 {
1581 struct fish *fish = &world.fishes[i];
1582
1583 if( fish->state == k_fish_state_alive )
1584 {
1585 v2i_add( fish->pos, fish->dir, fish->pos );
1586 struct cell *cell_current = pcell( fish->pos );
1587
1588 if( cell_current->state & FLAG_IS_TRIGGER )
1589 {
1590 int trigger_id = cell_current->links[0]?0:1;
1591
1592 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
1593
1594 cell_current->state |= FLAG_TRIGGERED;
1595
1596 if( trigger_id )
1597 target_peice->state |= FLAG_FLIP_FLOP;
1598 else
1599 target_peice->state &= ~FLAG_FLIP_FLOP;
1600 }
1601 }
1602 }
1603
1604 // Third pass (collisions)
1605 struct fish *fi, *fj;
1606
1607 for( int i = 0; i < world.num_fishes; i ++ )
1608 {
1609 fi = &world.fishes[i];
1610
1611 if( fi->state == k_fish_state_alive )
1612 {
1613 int continue_again = 0;
1614
1615 for( int j = i+1; j < world.num_fishes; j ++ )
1616 {
1617 fj = &world.fishes[j];
1618
1619 if( (fj->state == k_fish_state_alive) )
1620 {
1621 v2i fi_prev;
1622 v2i fj_prev;
1623
1624 v2i_sub( fi->pos, fi->dir, fi_prev );
1625 v2i_sub( fj->pos, fj->dir, fj_prev );
1626
1627 int
1628 collide_next_frame = (
1629 (fi->pos[0] == fj->pos[0]) &&
1630 (fi->pos[1] == fj->pos[1]))? 1: 0,
1631 collide_this_frame = (
1632 (fi_prev[0] == fj->pos[0]) &&
1633 (fi_prev[1] == fj->pos[1]) &&
1634 (fj_prev[0] == fi->pos[0]) &&
1635 (fj_prev[1] == fi->pos[1])
1636 )? 1: 0;
1637
1638 if( collide_next_frame || collide_this_frame )
1639 {
1640 sw_set_achievement( "BANG" );
1641
1642 // Shatter death (+0.5s)
1643 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
1644
1645 fi->state = k_fish_state_soon_dead;
1646 fj->state = k_fish_state_soon_dead;
1647 fi->death_time = death_time;
1648 fj->death_time = death_time;
1649
1650 continue_again = 1;
1651 break;
1652 }
1653 }
1654 }
1655 if( continue_again )
1656 continue;
1657 }
1658 }
1659
1660 // Spawn fishes
1661 for( int i = 0; i < arrlen( world.io ); i ++ )
1662 {
1663 struct cell_terminal *term = &world.io[ i ];
1664 int is_input = pcell(term->pos)->state & FLAG_INPUT;
1665
1666 if( is_input )
1667 {
1668 if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
1669 {
1670 char emit = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
1671 if( emit == ' ' )
1672 continue;
1673
1674 struct fish *fish = &world.fishes[ world.num_fishes ];
1675 v2i_copy( term->pos, fish->pos );
1676
1677 fish->state = k_fish_state_alive;
1678 fish->payload = emit;
1679
1680 struct cell *cell_ptr = pcell( fish->pos );
1681
1682 if( cell_ptr->config != k_cell_type_stub )
1683 {
1684 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
1685
1686 v2i_copy( desc->start, fish->dir );
1687 fish->flow_reversed = 1;
1688
1689 world.num_fishes ++;
1690 alive_count ++;
1691 }
1692 }
1693 }
1694 }
1695
1696 if( alive_count == 0 )
1697 {
1698 world.completed = 1;
1699
1700 for( int i = 0; i < arrlen( world.io ); i ++ )
1701 {
1702 struct cell_terminal *term = &world.io[ i ];
1703 int is_input = pcell(term->pos)->state & FLAG_INPUT;
1704
1705 if( !is_input )
1706 {
1707 struct terminal_run *run = &term->runs[ world.sim_run ];
1708
1709 if( run->recv_count == run->condition_count )
1710 {
1711 for( int j = 0; j < run->condition_count; j ++ )
1712 {
1713 if( run->recieved[j] != run->conditions[j] )
1714 {
1715 world.completed = 0;
1716 break;
1717 }
1718 }
1719 }
1720 else
1721 {
1722 world.completed = 0;
1723 break;
1724 }
1725 }
1726 }
1727
1728 if( world.completed )
1729 {
1730 if( world.sim_run < world.max_runs-1 )
1731 {
1732 vg_success( "Run passed, starting next\n" );
1733 world.sim_run ++;
1734 world.sim_frame = 0;
1735 world.sim_target = 0;
1736 world.num_fishes = 0;
1737
1738 // Reset timing reference points
1739 world.sim_delta_ref = vg_time;
1740 world.sim_internal_ref = 0.0f;
1741
1742 if( world.buttons[ k_world_button_pause ].pressed )
1743 world.pause_offset_target = 0.5f;
1744 else
1745 world.pause_offset_target = 0.0f;
1746
1747 world.sim_internal_time = 0.0f;
1748
1749 for( int i = 0; i < world.w*world.h; i ++ )
1750 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
1751
1752 continue;
1753 }
1754 else
1755 {
1756 vg_success( "Level passed!\n" );
1757
1758 u32 score = 0;
1759 for( int i = 0; i < world.w*world.h; i ++ )
1760 if( world.data[ i ].state & FLAG_CANAL )
1761 score ++;
1762
1763 world.score = score;
1764 world.time = world.sim_frame;
1765
1766 // Copy into career data
1767 if( world.pCmpLevel )
1768 {
1769 career_pass_level( world.pCmpLevel, world.score, 1 );
1770 }
1771 }
1772 }
1773 else
1774 {
1775 if( world.sim_run > 0 )
1776 sw_set_achievement( "GOOD_ENOUGH" );
1777
1778 vg_error( "Level failed :(\n" );
1779 }
1780
1781 simulation_stop();
1782 break;
1783 }
1784
1785 world.sim_frame ++;
1786 }
1787
1788 // Sounds
1789 if( failure_this_frame )
1790 {
1791 sfx_set_play( &audio_tones, &audio_system_balls_extra, 0 );
1792 }
1793 else if( success_this_frame )
1794 {
1795 static int succes_counter = 0;
1796
1797 sfx_set_play( &audio_tones, &audio_system_balls_extra, 1+(succes_counter++) );
1798
1799 if( succes_counter == 7 )
1800 succes_counter = 0;
1801 }
1802
1803 // Position update
1804 // =====================================================================================================
1805
1806 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
1807
1808 for( int i = 0; i < world.num_fishes; i ++ )
1809 {
1810 struct fish *fish = &world.fishes[i];
1811
1812 if( fish->state == k_fish_state_dead )
1813 continue;
1814
1815 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
1816 continue; // Todo: particle thing?
1817
1818 struct cell *cell = pcell(fish->pos);
1819 struct cell_description *desc = &cell_descriptions[ cell->config ];
1820
1821 v2f const *curve;
1822
1823 float t = world.frame_lerp;
1824 if( fish->flow_reversed && !desc->is_linear )
1825 t = 1.0f-t;
1826
1827 v2_copy( fish->physics_co, fish->physics_v );
1828
1829 switch( cell->config )
1830 {
1831 case k_cell_type_merge:
1832 if( fish->dir[0] == 1 )
1833 curve = curve_12;
1834 else
1835 curve = curve_9;
1836 break;
1837 case k_cell_type_con_r: curve = curve_1; break;
1838 case k_cell_type_con_l: curve = curve_4; break;
1839 case k_cell_type_con_u: curve = curve_2; break;
1840 case k_cell_type_con_d: curve = curve_8; break;
1841 case 3: curve = curve_3; break;
1842 case 6: curve = curve_6; break;
1843 case 9: curve = curve_9; break;
1844 case 12: curve = curve_12; break;
1845 case 7:
1846 if( t > curve_7_linear_section )
1847 {
1848 t -= curve_7_linear_section;
1849 t *= (1.0f/(1.0f-curve_7_linear_section));
1850
1851 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
1852 }
1853 else curve = NULL;
1854 break;
1855 default: curve = NULL; break;
1856 }
1857
1858 if( curve )
1859 {
1860 float t2 = t * t;
1861 float t3 = t * t * t;
1862
1863 float cA = 3.0f*t2 - 3.0f*t3;
1864 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
1865 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
1866
1867 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
1868 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
1869 fish->physics_co[0] += (float)fish->pos[0];
1870 fish->physics_co[1] += (float)fish->pos[1];
1871 }
1872 else
1873 {
1874 v2f origin;
1875 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
1876 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
1877
1878 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
1879 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
1880 }
1881
1882 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
1883 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
1884 }
1885 }
1886 }
1887
1888 void leaderboard_found( LeaderboardFindResult_t *pCallback );
1889 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback );
1890
1891 static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
1892 {
1893 v2i full_start = { 0,0 };
1894 v2i full_end = { world.w, world.h };
1895
1896 if( !start || !end )
1897 {
1898 start = full_start;
1899 end = full_end;
1900 }
1901
1902 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1903
1904 for( int y = start[1]; y < end[1]; y ++ )
1905 {
1906 for( int x = start[0]; x < end[0]; x ++ )
1907 {
1908 struct cell *cell = pcell((v2i){x,y});
1909 int selected = world.selected == y*world.w + x;
1910
1911 int tile_offsets[][2] =
1912 {
1913 {2, 0}, {0, 3}, {0, 2}, {2, 2},
1914 {1, 0}, {2, 3}, {3, 2}, {1, 3},
1915 {3, 1}, {0, 1}, {1, 2}, {2, 1},
1916 {1, 1}, {3, 3}, {2, 1}, {2, 1}
1917 };
1918
1919 int uv[2] = { 3, 0 };
1920
1921 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1922 {
1923 uv[0] = tile_offsets[ cell->config ][0];
1924 uv[1] = tile_offsets[ cell->config ][1];
1925 } else continue;
1926
1927 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
1928 if( selected )
1929 {
1930 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
1931 draw_mesh( 0, 2 );
1932 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1933 }
1934 else
1935 draw_mesh( 0, 2 );
1936 }
1937 }
1938 }
1939
1940 static void wbutton_run( enum e_world_button btn_name, v2f btn_tex )
1941 {
1942 static v3f button_colours[] = {
1943 {0.204f, 0.345f, 0.553f},
1944 {0.204f, 0.345f, 0.553f},
1945 {0.553f, 0.345f, 0.204f},
1946 {1.0f, 0.0f, 0.0f}
1947 };
1948
1949 struct cell_button *btn = &world.buttons[btn_name];
1950
1951 // Interaction
1952 int tex_offset = 0;
1953
1954 int is_hovering = (world.tile_x == world.w-1 && world.tile_y == world.h-btn_name-2)?1:0;
1955 if( vg_get_button_up( "primary" ) && is_hovering )
1956 {
1957 // Click event
1958 if( btn_name == k_world_button_sim )
1959 {
1960 if( world.buttons[ k_world_button_pause ].pressed )
1961 {
1962 if( !btn->pressed )
1963 {
1964 btn->pressed = 1;
1965 simulation_start();
1966
1967 world.pause_offset_target = 0.5f;
1968 }
1969 else
1970 world.pause_offset_target += 1.0f;
1971 }
1972 else
1973 {
1974 btn->pressed ^= 0x1;
1975
1976 if( btn->pressed )
1977 simulation_start();
1978 else
1979 simulation_stop();
1980 }
1981 }
1982 else if( btn_name == k_world_button_pause )
1983 {
1984 btn->pressed ^= 0x1;
1985
1986 world.sim_internal_ref = world.sim_internal_time;
1987 world.sim_delta_ref = vg_time;
1988
1989 if( btn->pressed )
1990 {
1991 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
1992 world.pause_offset_target = 0.5f - time_frac;
1993 }
1994 else
1995 world.pause_offset_target = 0.0f;
1996 }
1997 else if( btn_name == k_world_button_speedy )
1998 {
1999 btn->pressed ^= 0x1;
2000
2001 world.sim_delta_speed = btn->pressed? 10.0f: 2.5f;
2002 world.sim_delta_ref = vg_time;
2003 world.sim_internal_ref = world.sim_internal_time;
2004 }
2005 else
2006 {
2007 btn->pressed ^= 0x1;
2008 }
2009
2010 sfx_set_play( &audio_clicks, &audio_system_ui, btn->pressed?1:0 );
2011 }
2012
2013 // Drawing
2014 if( btn->pressed )
2015 {
2016 if( is_hovering )
2017 {
2018 btn->light_target = 0.9f;
2019 }
2020 else
2021 {
2022 if( btn_name == k_world_button_sim && world.buttons[ k_world_button_pause ].pressed )
2023 btn->light_target = fabsf(sinf( vg_time * 2.0f )) * 0.3f + 0.3f;
2024 else
2025 btn->light_target = 0.8f;
2026 }
2027 }
2028 else
2029 {
2030 btn->light_target = is_hovering? 0.2f: 0.0f;
2031 }
2032
2033 if( vg_get_button( "primary" ) && is_hovering )
2034 btn->light_target = 1.0f;
2035
2036 btn->light = vg_lerpf( btn->light, btn->light_target, vg_time_delta*26.0f );
2037
2038 // Draw
2039 if( btn_name == k_world_button_sim && world.buttons[ k_world_button_sim ].pressed )
2040 {
2041 if( world.buttons[ k_world_button_pause ].pressed )
2042 tex_offset = 3;
2043 else
2044 tex_offset = 2;
2045 }
2046
2047 v4f final_colour;
2048 v3_copy( button_colours[ btn_name ], final_colour );
2049 final_colour[3] = btn->light;
2050
2051 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2052 world.w-1,
2053 world.h-btn_name-2,
2054 (float)(btn_tex[0]+tex_offset),
2055 btn_tex[1]
2056 );
2057 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2058
2059 draw_mesh( 0, 2 );
2060 }
2061
2062 static void wbutton_draw( v2i pos, v2f tex, v4f colour )
2063 {
2064 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2065 pos[0],
2066 pos[1],
2067 tex[0],
2068 tex[1]
2069 );
2070 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, colour );
2071 draw_mesh( 0, 2 );
2072 }
2073
2074 static void level_selection_buttons(void)
2075 {
2076 v3f tutorial_colour = { 0.204f, 0.345f, 0.553f };
2077 v3f locked_colour = { 0.2f, 0.2f, 0.2f };
2078
2079 struct button_grid
2080 {
2081 v3f primary_colour;
2082 v2i origin;
2083 v2i dims;
2084 struct cmp_level *levels;
2085 int count;
2086 }
2087 grids[] =
2088 {
2089 {
2090 .primary_colour = { 0.204f, 0.345f, 0.553f },
2091 .origin = { -1, 2 },
2092 .dims = { 1, 4 },
2093 .levels = cmp_levels_tutorials,
2094 .count = vg_list_size( cmp_levels_tutorials )
2095 },
2096 {
2097 .primary_colour = { 0.304f, 0.245f, 0.553f },
2098 .origin = { -5, 2 },
2099 .dims = { 3, 6 },
2100 .levels = cmp_levels_basic,
2101 .count = vg_list_size( cmp_levels_basic )
2102 },
2103 {
2104 .primary_colour = { 0.553f, 0.345f, 0.204f },
2105 .origin = { -4, 0 },
2106 .dims = { 4, 1 },
2107 .levels = cmp_levels_grad,
2108 .count = vg_list_size( cmp_levels_grad )
2109 }
2110 };
2111
2112 v2f tex_coord = { 0.0f, 0.0f };
2113 v4f final_colour = { 0.0f, 0.0f, 0.0f, 0.2f };
2114 v2i button_pos;
2115 static struct cmp_level *select_from = NULL;
2116 struct cmp_level *switch_level_to = NULL;
2117
2118 if( vg_get_button_down( "primary" ) )
2119 select_from = NULL;
2120
2121 for( int i = 0; i < vg_list_size( grids ); i ++ )
2122 {
2123 struct button_grid *grid = &grids[i];
2124
2125 int j = 0;
2126
2127 for( int x = 0; x < grid->dims[0]; x ++ )
2128 {
2129 for( int y = 0; y < grid->dims[1]; y ++ )
2130 {
2131 if( j < grid->count )
2132 {
2133 struct cmp_level *lvl = &grid->levels[ j ];
2134
2135 // Determine colour
2136 if( lvl->unlocked )
2137 {
2138 if( lvl->is_tutorial )
2139 v3_copy( tutorial_colour, final_colour );
2140 else
2141 v3_copy( grid->primary_colour, final_colour );
2142
2143 if( lvl->completed_score )
2144 final_colour[3] = 0.8f;
2145 else
2146 final_colour[3] = 0.2f;
2147 }
2148 else v3_copy( locked_colour, final_colour );
2149
2150 v2i_add( grid->origin, (v2i){ x,y }, button_pos );
2151 int is_hovering = v2i_eq( (v2i){world.tile_x, world.tile_y}, button_pos );
2152
2153 if( is_hovering )
2154 {
2155 final_colour[3] += 0.1f;
2156
2157 // Up click
2158 if( vg_get_button_up( "primary" ) )
2159 if( select_from == lvl && lvl->unlocked )
2160 {
2161 switch_level_to = lvl;
2162 sfx_set_play( &audio_clicks, &audio_system_ui, 1 );
2163 }
2164
2165 // Start click
2166 if( vg_get_button_down( "primary" ) )
2167 select_from = lvl;
2168
2169 if( vg_get_button( "primary" ) )
2170 final_colour[3] += 0.2f;
2171 }
2172
2173 if( world.pCmpLevel == lvl )
2174 final_colour[3] += fabsf(sinf( vg_time * 2.0f )) * 0.2f;
2175
2176 wbutton_draw( (v2i){ grid->origin[0] + x, grid->origin[1] + y }, tex_coord, final_colour );
2177 }
2178 else break;
2179
2180 j ++;
2181 }
2182 }
2183 }
2184
2185 if( switch_level_to )
2186 {
2187 if( console_changelevel( 1, &switch_level_to->map_name ) )
2188 {
2189 world.pCmpLevel = switch_level_to;
2190 }
2191 }
2192 }
2193
2194 void vg_render(void)
2195 {
2196 glViewport( 0,0, vg_window_x, vg_window_y );
2197
2198 glDisable( GL_DEPTH_TEST );
2199 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
2200 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2201
2202 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2203 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2204
2205 int const circle_base = 6;
2206 int const filled_start = circle_base+0;
2207 int const filled_count = circle_base+32;
2208 int const empty_start = circle_base+32;
2209 int const empty_count = circle_base+32*2;
2210
2211 if( !world.initialzed )
2212 return;
2213
2214 // BACKGROUND
2215 // ========================================================================================================
2216 use_mesh( &world.shapes );
2217
2218 SHADER_USE( shader_background );
2219 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2220
2221 glActiveTexture( GL_TEXTURE0 );
2222 glBindTexture( GL_TEXTURE_2D, world.background_data );
2223 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2224
2225 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2226 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
2227
2228 glActiveTexture( GL_TEXTURE1 );
2229 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2230 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2231
2232 draw_mesh( 0, 2 );
2233
2234 // TILESET BACKGROUND LAYER
2235 // ========================================================================================================
2236 use_mesh( &world.shapes );
2237 SHADER_USE( shader_tile_main );
2238
2239 m2x2f subtransform;
2240 m2x2_identity( subtransform );
2241 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2242 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2243 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2244 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2245
2246 glEnable(GL_BLEND);
2247 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2248 glBlendEquation(GL_FUNC_ADD);
2249
2250 // Bind textures
2251 vg_tex2d_bind( &tex_tile_data, 0 );
2252 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2253
2254 vg_tex2d_bind( &tex_wood, 1 );
2255 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2256
2257 render_tiles( NULL, NULL, colour_default, colour_default );
2258
2259 // MARBLES
2260 // ========================================================================================================
2261 SHADER_USE( shader_ball );
2262 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2263
2264 vg_tex2d_bind( &tex_ball_noise, 0 );
2265 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
2266
2267 if( get_wbutton( k_world_button_sim )->pressed )
2268 {
2269 for( int i = 0; i < world.num_fishes; i ++ )
2270 {
2271 struct fish *fish = &world.fishes[i];
2272 v3f render_pos;
2273 render_pos[2] = 1.0f;
2274
2275 if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
2276 {
2277 float death_anim_time = world.sim_internal_time - fish->death_time;
2278
2279 // Death animation
2280 if( death_anim_time > 0.0f && death_anim_time < 1.0f )
2281 {
2282 float amt = 1.0f-death_anim_time*death_anim_time;
2283
2284 v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
2285 render_pos[2] = amt;
2286 }
2287 else if( world.sim_internal_time > fish->death_time )
2288 continue;
2289 }
2290 else if( fish->state == k_fish_state_bg )
2291 continue;
2292
2293 v2_copy( fish->physics_co, render_pos );
2294
2295 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2296 colour_code_v3( fish->payload, dot_colour );
2297
2298 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
2299 glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
2300 glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
2301 draw_mesh( 0, 2 );
2302 }
2303 }
2304
2305 // TILESET FOREGROUND LAYER
2306 // ========================================================================================================
2307 SHADER_USE( shader_tile_main );
2308
2309 // Bind textures
2310 vg_tex2d_bind( &tex_tile_data, 0 );
2311 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2312
2313 vg_tex2d_bind( &tex_wood, 1 );
2314 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2315
2316 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
2317 render_tiles( NULL, NULL, colour_default, colour_selected );
2318
2319 // Draw splitters
2320 for( int y = 2; y < world.h-2; y ++ )
2321 {
2322 for( int x = 2; x < world.w-2; x ++ )
2323 {
2324 struct cell *cell = pcell((v2i){x,y});
2325
2326 if( cell->state & FLAG_CANAL )
2327 {
2328 if( cell->config == k_cell_type_split )
2329 {
2330 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
2331
2332 if( cell->state & FLAG_FLIP_ROTATING )
2333 {
2334 if( (world.frame_lerp > curve_7_linear_section) )
2335 {
2336 float const rotation_speed = 0.4f;
2337 if( (world.frame_lerp < 1.0f-rotation_speed) )
2338 {
2339 float t = world.frame_lerp - curve_7_linear_section;
2340 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
2341 t += 1.0f;
2342
2343 rotation *= t;
2344 }
2345 else
2346 rotation *= -1.0f;
2347 }
2348 }
2349
2350 m2x2_create_rotation( subtransform, rotation );
2351
2352 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2353 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2354 (float)x,
2355 (float)y + 0.125f,
2356 cell->state & FLAG_TARGETED? 3.0f: 0.0f,
2357 0.0f
2358 );
2359 draw_mesh( 0, 2 );
2360 }
2361 }
2362 }
2363 }
2364
2365 // EDIT OVERLAY
2366 // ========================================================================================================
2367 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) && !world.id_drag_from )
2368 {
2369 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
2370 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
2371
2372 world.data[ world.selected ].state ^= FLAG_CANAL;
2373 map_reclassify( new_begin, new_end, 0 );
2374
2375 m2x2_identity( subtransform );
2376 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
2377 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2378 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
2379
2380 render_tiles( new_begin, new_end, colour_default, colour_default );
2381
2382 world.data[ world.selected ].state ^= FLAG_CANAL;
2383 map_reclassify( new_begin, new_end, 0 );
2384 }
2385
2386 // BUTTONS
2387 // ========================================================================================================
2388 SHADER_USE( shader_buttons );
2389 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2390
2391 vg_tex2d_bind( &tex_buttons, 0 );
2392 glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
2393
2394 wbutton_run( k_world_button_sim, (v2f){ 0.0f, 3.0f } );
2395 wbutton_run( k_world_button_pause, (v2f){ 1.0f, 3.0f } );
2396 wbutton_run( k_world_button_speedy, (v2f){ 0.0f, 2.0f } );
2397
2398 level_selection_buttons();
2399
2400 // WIRES
2401 // ========================================================================================================
2402 //glDisable(GL_BLEND);
2403
2404 SHADER_USE( shader_wire );
2405 glBindVertexArray( world.wire.vao );
2406
2407 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2408
2409 v4f const wire_left_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
2410 v4f const wire_right_colour = { 0.2f, 0.2f, 0.2f, 1.0f };
2411 v4f const wire_drag_colour = { 0.2f, 0.2f, 0.2f, 0.6f };
2412
2413 glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
2414 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
2415
2416 if( world.id_drag_from )
2417 {
2418 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
2419 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
2420 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f );
2421 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f );
2422 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2423 }
2424
2425 // Pulling animation
2426 float rp_x1 = world.frame_lerp*9.0f;
2427 float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
2428 float rp_x2 = 1.0f-rp_xa;
2429
2430 for( int y = 2; y < world.h-2; y ++ )
2431 {
2432 for( int x = 2; x < world.w-2; x ++ )
2433 {
2434 struct cell *cell = pcell((v2i){x,y});
2435
2436 if( cell->state & FLAG_CANAL )
2437 {
2438 if( cell->state & FLAG_IS_TRIGGER )
2439 {
2440 struct cell_description *desc = &cell_descriptions[ cell->config ];
2441
2442 int trigger_id = cell->links[0]?0:1;
2443
2444 int x2 = cell->links[trigger_id] % world.w;
2445 int y2 = (cell->links[trigger_id] - x2) / world.w;
2446
2447 v2f startpoint;
2448 v2f endpoint;
2449
2450 startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2451 startpoint[1] = (float)y2 + 0.25f;
2452
2453 endpoint[0] = x;
2454 endpoint[1] = y;
2455
2456 v2_add( desc->trigger_pos, endpoint, endpoint );
2457
2458 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
2459 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
2460 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
2461 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f );
2462 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f );
2463 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2464 }
2465 }
2466 }
2467 }
2468
2469 // WIRE ENDPOINTS
2470 // ========================================================================================================
2471
2472 SHADER_USE( shader_tile_colour );
2473 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2474 use_mesh( &world.shapes );
2475
2476 for( int y = 2; y < world.h-2; y ++ )
2477 {
2478 for( int x = 2; x < world.w-2; x ++ )
2479 {
2480 struct cell *cell = pcell((v2i){x,y});
2481
2482 if( cell->state & FLAG_CANAL )
2483 {
2484 if( cell->state & FLAG_IS_TRIGGER )
2485 {
2486 struct cell_description *desc = &cell_descriptions[ cell->config ];
2487
2488 int trigger_id = cell->links[0]?0:1;
2489
2490 int x2 = cell->links[trigger_id] % world.w;
2491 int y2 = (cell->links[trigger_id] - x2) / world.w;
2492
2493 v2f pts[2];
2494
2495 pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2496 pts[0][1] = (float)y2 + 0.25f;
2497
2498 pts[1][0] = x;
2499 pts[1][1] = y;
2500
2501 v2_add( desc->trigger_pos, pts[1], pts[1] );
2502
2503 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2504 1, trigger_id? wire_right_colour: wire_left_colour );
2505
2506 for( int i = 0; i < 2; i ++ )
2507 {
2508 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2509 pts[i][0],
2510 pts[i][1],
2511 0.08f
2512 );
2513 draw_mesh( filled_start, filled_count );
2514 }
2515 }
2516 }
2517 }
2518 }
2519
2520 // SUB SPLITTER DIRECTION
2521 // ========================================================================================================
2522
2523 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
2524
2525 for( int y = 2; y < world.h-2; y ++ )
2526 {
2527 for( int x = 2; x < world.w-2; x ++ )
2528 {
2529 struct cell *cell = pcell((v2i){x,y});
2530
2531 if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
2532 {
2533 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f );
2534 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
2535 }
2536 }
2537 }
2538
2539 // I/O ARRAYS
2540 // ========================================================================================================
2541
2542 //glEnable(GL_BLEND);
2543
2544 for( int i = 0; i < arrlen( world.io ); i ++ )
2545 {
2546 struct cell_terminal *term = &world.io[ i ];
2547 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2548
2549 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2550
2551 for( int k = 0; k < term->run_count; k ++ )
2552 {
2553 float arr_base = is_input? 1.2f: -0.2f,
2554 run_offset = (is_input? 0.2f: -0.2f) * (float)k,
2555 y_position = is_input?
2556 (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
2557 (float)term->pos[1] + arr_base + run_offset;
2558
2559 v4f bar_colour;
2560 int bar_draw = 0;
2561
2562 if( is_simulation_running() )
2563 {
2564 if( k == world.sim_run )
2565 {
2566 float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
2567
2568 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
2569 }
2570 else
2571 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2572
2573 bar_draw = 1;
2574 }
2575 else if( 1 || k & 0x1 )
2576 {
2577 if( k & 0x1 )
2578 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
2579 else
2580 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2581
2582 bar_draw = 1;
2583 }
2584
2585 if( bar_draw )
2586 {
2587 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
2588 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)term->pos[0], y_position - 0.1f, 1.0f );
2589 draw_mesh( 2, 2 );
2590 }
2591
2592 for( int j = 0; j < term->runs[k].condition_count; j ++ )
2593 {
2594 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2595 (float)term->pos[0] + 0.2f + 0.2f * (float)j,
2596 y_position,
2597 0.1f
2598 );
2599
2600 if( is_input )
2601 {
2602 char cc = term->runs[k].conditions[j];
2603 if( cc != ' ' )
2604 {
2605 colour_code_v3( cc, dot_colour );
2606 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2607
2608 // Draw filled if tick not passed, draw empty if empty
2609 if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
2610 draw_mesh( empty_start, empty_count );
2611 else
2612 draw_mesh( filled_start, filled_count );
2613 }
2614 }
2615 else
2616 {
2617
2618 if( term->runs[k].recv_count > j )
2619 {
2620 colour_code_v3( term->runs[k].recieved[j], dot_colour );
2621 v3_muls( dot_colour, 0.8f, dot_colour );
2622 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2623
2624 draw_mesh( filled_start, filled_count );
2625 }
2626
2627 colour_code_v3( term->runs[k].conditions[j], dot_colour );
2628 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2629
2630 draw_mesh( empty_start, empty_count );
2631 }
2632 }
2633 }
2634 }
2635
2636 glDisable(GL_BLEND);
2637
2638 // Draw score
2639 /*
2640 float const score_bright = 1.25f;
2641 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2642 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
2643
2644 use_mesh( &world.numbers );
2645 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
2646 */
2647 }
2648
2649 static ui_colourset flcol_list_a = {
2650 .main = 0xff877979,
2651 .hover = 0xffa09393,
2652 .active = 0xffbfb1b0
2653 };
2654 static ui_colourset flcol_list_b = {
2655 .main = 0xff7c6e6e,
2656 .hover = 0xffa09393,
2657 .active = 0xffbfb1b0
2658 };
2659
2660 static ui_colourset flcol_list_complete_a = {
2661 .main = 0xff62a064,
2662 .hover = 0xff8dc18f,
2663 .active = 0xffb2ddb3
2664 };
2665
2666 static ui_colourset flcol_list_complete_b = {
2667 .main = 0xff79b37b,
2668 .hover = 0xff8dc18f,
2669 .active = 0xffb2ddb3
2670 };
2671
2672 static ui_colourset flcol_list_locked = {
2673 .main = 0xff655959,
2674 .hover = 0xff655959,
2675 .active = 0xff655959
2676 };
2677
2678 static struct
2679 {
2680 SteamLeaderboard_t steam_leaderboard;
2681 int leaderboard_show;
2682
2683 struct leaderboard_player
2684 {
2685 // Internal
2686 u64_steamid id;
2687 i32 score;
2688 int is_local_player;
2689
2690 // To be displayed
2691 char score_text[ 16 ];
2692 char player_name[ 48 ];
2693 GLuint texture; // Not dynamic
2694 }
2695 leaderboard_players[10];
2696 int leaderboard_count;
2697
2698 struct
2699 {
2700 struct cmp_level *level;
2701
2702 i32 score;
2703 int is_waiting;
2704 }
2705 upload_request;
2706
2707 struct cmp_level *level_selected;
2708 }
2709 ui_data;
2710
2711 void vg_ui(void)
2712 {
2713 /*
2714 // UI memory
2715 static int pack_selection = 0;
2716 static struct pack_info
2717 {
2718 struct cmp_level *levels;
2719 u32 level_count;
2720 const char *name;
2721 }
2722 pack_infos[] =
2723 {
2724 {
2725 .levels = cmp_levels_tutorials,
2726 .level_count = vg_list_size(cmp_levels_tutorials),
2727 .name = "Training"
2728 },
2729 {
2730 .levels = cmp_levels_basic,
2731 .level_count = vg_list_size(cmp_levels_basic),
2732 .name = "Main"
2733 },
2734 {
2735 .levels = cmp_levels_grad,
2736 .level_count = vg_list_size(cmp_levels_tutorials),
2737 .name = "Expert"
2738 }
2739 };
2740
2741 // UI Code
2742 ui_global_ctx.cursor[0] = 0;
2743 ui_global_ctx.cursor[1] = 0;
2744 ui_global_ctx.cursor[2] = 256;
2745
2746 gui_fill_y();
2747
2748 ui_global_ctx.id_base = 4 << 16;
2749
2750 gui_new_node();
2751 {
2752 gui_capture_mouse( 9999 );
2753 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2754
2755 gui_text( "ASSIGNMENTS", 32, 0 );
2756
2757 ui_global_ctx.cursor[1] += 30;
2758 ui_global_ctx.cursor[3] = 25;
2759
2760 gui_new_node();
2761 {
2762 ui_rect_pad( ui_global_ctx.cursor, 2 );
2763 ui_global_ctx.cursor[2] = 84;
2764
2765 for( int i = 0; i < 3; i ++ )
2766 {
2767 int pack_is_unlocked = pack_infos[i].levels[0].unlocked;
2768
2769 if( i == pack_selection || !pack_is_unlocked )
2770 gui_override_colours( &flcol_list_locked );
2771
2772 if( gui_button( 2000 + i ) == k_button_click && pack_is_unlocked )
2773 pack_selection = i;
2774
2775 ui_global_ctx.cursor[1] += 2;
2776 gui_text( pack_is_unlocked? pack_infos[i].name: "???", 24, 0 );
2777 gui_end_right();
2778
2779 gui_reset_colours();
2780 }
2781 }
2782 gui_end_down();
2783
2784 ui_global_ctx.cursor[3] = 500;
2785
2786 // DRAW LEVEL SELECTION LIST
2787 {
2788 struct cmp_level *levels = pack_infos[ pack_selection ].levels;
2789 int count = pack_infos[ pack_selection ].level_count;
2790
2791 static struct ui_scrollbar sb = {
2792 .bar_height = 400
2793 };
2794
2795 ui_px view_height = ui_global_ctx.cursor[3];
2796 ui_px level_height = 50;
2797
2798 // Level scroll view
2799 gui_new_node();
2800 {
2801 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2802 gui_set_clip( ui_global_ctx.cursor );
2803
2804 ui_global_ctx.cursor[2] = 14;
2805 gui_align_right();
2806
2807 ui_px content_height = count*level_height;
2808 if( content_height > view_height )
2809 {
2810 ui_scrollbar( &ui_global_ctx, &sb, 1 );
2811 ui_global_ctx.cursor[1] -= ui_calculate_content_scroll( &sb, content_height );
2812 }
2813 else
2814 {
2815 gui_fill_rect( ui_global_ctx.cursor, 0xff807373 );
2816 }
2817
2818 ui_global_ctx.cursor[2] = 240;
2819 ui_global_ctx.cursor[3] = level_height;
2820 gui_align_left();
2821
2822 for( int i = 0; i < count; i ++ )
2823 {
2824 struct cmp_level *lvl_info = &levels[i];
2825
2826 if( lvl_info->unlocked )
2827 {
2828 if( lvl_info->completed_score != 0 )
2829 gui_override_colours( i&0x1? &flcol_list_complete_a: &flcol_list_complete_b );
2830 else
2831 gui_override_colours( i&0x1? &flcol_list_a: &flcol_list_b );
2832 }
2833 else
2834 gui_override_colours( &flcol_list_locked );
2835
2836 if( lvl_info->unlocked )
2837 {
2838 if( gui_button( 2 + i ) == k_button_click )
2839 {
2840 ui_data.level_selected = &levels[i];
2841 ui_data.leaderboard_show = 0;
2842
2843 if( pack_selection >= 1 )
2844 sw_find_leaderboard( ui_data.level_selected->map_name );
2845 }
2846
2847 ui_global_ctx.override_colour = 0xffffffff;
2848 gui_text( lvl_info->title, 24, 0 );
2849 ui_global_ctx.cursor[1] += 18;
2850 gui_text( lvl_info->completed_score>0? "passed": "incomplete", 24, 0 );
2851 }
2852 else
2853 {
2854 gui_button( 2 + i );
2855
2856 ui_global_ctx.override_colour = 0xff786f6f;
2857 gui_text( "???", 24, 0 );
2858 ui_global_ctx.cursor[1] += 18;
2859 gui_text( "locked", 24, 0 );
2860 }
2861
2862 gui_end_down();
2863 }
2864
2865 gui_reset_colours();
2866 gui_release_clip();
2867 }
2868 gui_end_down();
2869 }
2870 }
2871 gui_end_right();
2872
2873 // Selected level UI
2874 // ============================================================
2875
2876 if( ui_data.level_selected )
2877 {
2878 ui_global_ctx.cursor[0] += 16;
2879 ui_global_ctx.cursor[1] += 16;
2880 ui_global_ctx.cursor[2] = 512-40;
2881 ui_global_ctx.cursor[3] = 560-16;
2882
2883 gui_new_node();
2884 {
2885 gui_capture_mouse( 9999 );
2886
2887 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2888 ui_global_ctx.cursor[1] += 4;
2889 gui_text( ui_data.level_selected->title, 24, 0 );
2890
2891 ui_global_ctx.cursor[1] += 30;
2892 ui_rect_pad( ui_global_ctx.cursor, 8 );
2893 ui_global_ctx.cursor[3] = 300;
2894
2895 gui_new_node();
2896 {
2897 gui_fill_rect( ui_global_ctx.cursor, 0xff655959 );
2898 }
2899 gui_end_down();
2900
2901 //ui_text_use_paragraph( &ui_global_ctx );
2902 ui_global_ctx.cursor[1] += 2;
2903
2904 gui_text( ui_data.level_selected->description, 16, 0 );
2905 //ui_text_use_title( &ui_global_ctx );
2906
2907 // Buttons at the bottom
2908 ui_global_ctx.cursor[3] = 25;
2909 ui_global_ctx.cursor[2] = 80;
2910
2911 gui_align_bottom();
2912 ui_global_ctx.cursor[1] -= 8;
2913
2914 if( gui_button( 3000 ) == k_button_click )
2915 {
2916 ui_data.level_selected = NULL;
2917 }
2918 gui_text( "BACK", 24, k_text_alignment_center );
2919 gui_end();
2920
2921 gui_align_right();
2922 ui_global_ctx.cursor[2] = 170;
2923 ui_global_ctx.cursor[0] -= 8 + 170 + 2;
2924
2925 {
2926 gui_override_colours( &flcol_list_locked );
2927 if( gui_button( 3001 ) == k_button_click )
2928 vg_error( "UNIMPLEMENTED\n" );
2929
2930 ui_global_ctx.override_colour = 0xff888888;
2931
2932 gui_text( "RESTORE SOLUTION", 24, k_text_alignment_center );
2933 gui_end_right();
2934 ui_global_ctx.override_colour = 0xffffffff;
2935 }
2936
2937 ui_global_ctx.cursor[0] += 2;
2938 ui_global_ctx.cursor[2] = 80;
2939
2940 {
2941 gui_override_colours( &flcol_list_complete_a );
2942 if( gui_button( 3002 ) == k_button_click )
2943 {
2944 if( console_changelevel( 1, &ui_data.level_selected->map_name ) )
2945 {
2946 world.pCmpLevel = ui_data.level_selected;
2947
2948 ui_data.level_selected = NULL;
2949 ui_data.leaderboard_show = 0;
2950 }
2951 }
2952 gui_text( "PLAY", 24, k_text_alignment_center );
2953 gui_end();
2954 }
2955
2956 gui_reset_colours();
2957 }
2958 gui_end_right();
2959
2960 if( ui_data.leaderboard_show )
2961 {
2962 ui_global_ctx.cursor[0] += 16;
2963 ui_global_ctx.cursor[2] = 350;
2964 ui_global_ctx.cursor[3] = 25;
2965
2966 // If has results
2967 gui_new_node();
2968 {
2969 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2970 gui_text( "FRIEND LEADERBOARD", 24, 0 );
2971 }
2972 gui_end_down();
2973
2974 ui_global_ctx.cursor[1] += 2;
2975
2976 gui_new_node();
2977 {
2978 ui_global_ctx.cursor[3] = 32+8;
2979
2980 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
2981 {
2982 gui_new_node();
2983 {
2984 gui_fill_rect( ui_global_ctx.cursor, i&0x1? flcol_list_a.main: flcol_list_b.main );
2985
2986 ui_global_ctx.cursor[0] += 4;
2987 ui_global_ctx.cursor[1] += 4;
2988
2989 // 1,2,3 ...
2990 static const char *places[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
2991 gui_text( places[i], 24, 0 );
2992 ui_global_ctx.cursor[0] += 32;
2993
2994 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
2995
2996 // Players image
2997 ui_global_ctx.cursor[2] = 32;
2998 ui_global_ctx.cursor[3] = 32;
2999
3000 gui_new_node();
3001 {
3002 gui_push_image( ui_global_ctx.cursor, player->texture );
3003 }
3004 gui_end_right();
3005
3006 // Players name
3007 gui_text( player->player_name, 24, 0 );
3008
3009 ui_global_ctx.cursor[2] = 50;
3010 gui_align_right();
3011
3012 gui_text( player->score_text, 24, k_text_alignment_right );
3013 }
3014 gui_end_down();
3015
3016 ui_global_ctx.cursor[1] += 2;
3017 }
3018 }
3019 gui_end();
3020 }
3021 }
3022 */
3023 }
3024
3025 void leaderboard_dispatch_score(void)
3026 {
3027 #if STEAM_LEADERBOARDS
3028 sw_upload_leaderboard_score(
3029 ui_data.upload_request.level->steam_leaderboard,
3030 k_ELeaderboardUploadScoreMethodKeepBest,
3031 ui_data.upload_request.score,
3032 NULL,
3033 0
3034 );
3035
3036 ui_data.upload_request.is_waiting = 0;
3037
3038 vg_success( "Dispatched leaderboard score\n" );
3039 #endif
3040 }
3041
3042 void leaderboard_found( LeaderboardFindResult_t *pCallback )
3043 {
3044 #ifdef STEAM_LEADERBOARDS
3045 if( !pCallback->m_bLeaderboardFound )
3046 {
3047 vg_error( "Leaderboard could not be found\n" );
3048 ui_data.steam_leaderboard = 0;
3049 }
3050 else
3051 {
3052 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
3053
3054 // Update UI state and request entries if this callback found the current UI level
3055 if( ui_data.level_selected )
3056 {
3057 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
3058 {
3059 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
3060 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3061 }
3062 }
3063
3064 // Dispatch the waiting request if there was one
3065 if( ui_data.upload_request.is_waiting )
3066 {
3067 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
3068 {
3069 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3070 leaderboard_dispatch_score();
3071 }
3072 }
3073 }
3074 #endif
3075 }
3076
3077 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
3078 {
3079 #ifdef STEAM_LEADERBOARDS
3080 // Update UI if this leaderboard matches what we currently have in view
3081 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
3082 {
3083 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
3084 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
3085
3086 u64_steamid local_player = sw_get_steamid();
3087
3088 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
3089 {
3090 LeaderboardEntry_t entry;
3091 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
3092
3093 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
3094
3095 player->id = entry.m_steamIDUser.m_unAll64Bits;
3096 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
3097 player->score = entry.m_nScore;
3098
3099 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
3100 player->texture = sw_get_player_image( player->id );
3101
3102 if( player->texture == 0 )
3103 player->texture = tex_unkown.name;
3104
3105 player->is_local_player = local_player == player->id? 1: 0;
3106 }
3107
3108 if( ui_data.leaderboard_count )
3109 ui_data.leaderboard_show = 1;
3110 else
3111 ui_data.leaderboard_show = 0;
3112 }
3113 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
3114 #endif
3115 }
3116
3117 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
3118 {
3119 #ifdef STEAM_LEADERBOARDS
3120 if( ui_data.upload_request.is_waiting )
3121 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
3122
3123 ui_data.upload_request.level = cmp_level;
3124 ui_data.upload_request.score = score;
3125 ui_data.upload_request.is_waiting = 1;
3126
3127 // If leaderboard ID has been downloaded already then just immediately dispatch this
3128 if( cmp_level->steam_leaderboard )
3129 leaderboard_dispatch_score();
3130 else
3131 sw_find_leaderboard( cmp_level->map_name );
3132 #endif
3133 }
3134
3135 // CONSOLE COMMANDS
3136 // ===========================================================================================================
3137
3138 static int console_credits( int argc, char const *argv[] )
3139 {
3140 vg_info( "Aknowledgements:\n" );
3141 vg_info( " GLFW zlib/libpng glfw.org\n" );
3142 vg_info( " miniaudio MIT0 miniaud.io\n" );
3143 vg_info( " QOI MIT phoboslab.org\n" );
3144 vg_info( " STB library MIT nothings.org\n" );
3145 return 0;
3146 }
3147
3148 static int console_save_map( int argc, char const *argv[] )
3149 {
3150 if( !world.initialzed )
3151 {
3152 vg_error( "Tried to save uninitialized map!\n" );
3153 return 0;
3154 }
3155
3156 char map_path[ 256 ];
3157
3158 strcpy( map_path, "sav/" );
3159 strcat( map_path, world.map_name );
3160 strcat( map_path, ".map" );
3161
3162 FILE *test_writer = fopen( map_path, "wb" );
3163 if( test_writer )
3164 {
3165 vg_info( "Saving map to '%s'\n", map_path );
3166 map_serialize( test_writer );
3167
3168 fclose( test_writer );
3169 return 1;
3170 }
3171 else
3172 {
3173 vg_error( "Unable to open stream for writing\n" );
3174 return 0;
3175 }
3176 }
3177
3178 static int console_load_map( int argc, char const *argv[] )
3179 {
3180 char map_path[ 256 ];
3181
3182 if( argc >= 1 )
3183 {
3184 // try from saves
3185 strcpy( map_path, "sav/" );
3186 strcat( map_path, argv[0] );
3187 strcat( map_path, ".map" );
3188
3189 char *text_source = vg_textasset_read( map_path );
3190
3191 if( !text_source )
3192 {
3193 strcpy( map_path, "maps/" );
3194 strcat( map_path, argv[0] );
3195 strcat( map_path, ".map" );
3196
3197 text_source = vg_textasset_read( map_path );
3198 }
3199
3200 if( text_source )
3201 {
3202 vg_info( "Loading map: '%s'\n", map_path );
3203 world.pCmpLevel = NULL;
3204
3205 if( !map_load( text_source, argv[0] ) )
3206 {
3207 free( text_source );
3208 return 0;
3209 }
3210
3211 free( text_source );
3212 return 1;
3213 }
3214 else
3215 {
3216 vg_error( "Missing maps '%s'\n", argv[0] );
3217 return 0;
3218 }
3219 }
3220 else
3221 {
3222 vg_error( "Missing argument <map_path>\n" );
3223 return 0;
3224 }
3225 }
3226
3227 static int console_changelevel( int argc, char const *argv[] )
3228 {
3229 if( argc >= 1 )
3230 {
3231 // Save current level
3232 console_save_map( 0, NULL );
3233 if( console_load_map( argc, argv ) )
3234 {
3235 simulation_stop();
3236 return 1;
3237 }
3238 }
3239 else
3240 {
3241 vg_error( "Missing argument <map_path>\n" );
3242 }
3243
3244 return 0;
3245 }
3246
3247 // START UP / SHUTDOWN
3248 // ===========================================================================================================
3249
3250 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
3251 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
3252
3253 void vg_start(void)
3254 {
3255 // Steamworks callbacks
3256 #ifdef STEAM_LEADERBOARDS
3257 sw_leaderboard_found = &leaderboard_found;
3258 sw_leaderboard_downloaded = &leaderboard_downloaded;
3259 #endif
3260
3261 vg_function_push( (struct vg_cmd){
3262 .name = "_map_write",
3263 .function = console_save_map
3264 });
3265
3266 vg_function_push( (struct vg_cmd){
3267 .name = "_map_load",
3268 .function = console_load_map
3269 });
3270
3271 vg_function_push( (struct vg_cmd){
3272 .name = "map",
3273 .function = console_changelevel
3274 });
3275
3276 vg_function_push( (struct vg_cmd){
3277 .name = "credits",
3278 .function = console_credits
3279 });
3280
3281 // Combined quad, long quad / empty circle / filled circle mesh
3282 {
3283 float combined_mesh[6*6 + 32*6*3] = {
3284 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
3285 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
3286
3287 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
3288 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
3289
3290 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
3291 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
3292 };
3293
3294 float *circle_mesh = combined_mesh + 6*6;
3295 int const res = 32;
3296
3297 for( int i = 0; i < res; i ++ )
3298 {
3299 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
3300 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
3301
3302 circle_mesh[ i*6+0 ] = 0.0f;
3303 circle_mesh[ i*6+1 ] = 0.0f;
3304
3305 v2_copy( v0, circle_mesh + 32*6 + i*12 );
3306 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
3307 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
3308
3309 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
3310 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
3311 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
3312
3313 v2_copy( v0, circle_mesh + i*6+4 );
3314 v2_copy( v1, circle_mesh + i*6+2 );
3315 v2_copy( v0, circle_mesh+i*6+4 );
3316 v2_copy( v1, circle_mesh+i*6+2 );
3317 }
3318
3319 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
3320 }
3321
3322 // Create wire mesh
3323 {
3324 int const num_segments = 64;
3325
3326 struct mesh_wire *mw = &world.wire;
3327
3328 v2f wire_points[ num_segments * 2 ];
3329 u16 wire_indices[ 6*(num_segments-1) ];
3330
3331 for( int i = 0; i < num_segments; i ++ )
3332 {
3333 float l = (float)i / (float)(num_segments-1);
3334
3335 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
3336 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
3337
3338 if( i < num_segments-1 )
3339 {
3340 wire_indices[ i*6+0 ] = i*2 + 0;
3341 wire_indices[ i*6+1 ] = i*2 + 1;
3342 wire_indices[ i*6+2 ] = i*2 + 3;
3343 wire_indices[ i*6+3 ] = i*2 + 0;
3344 wire_indices[ i*6+4 ] = i*2 + 3;
3345 wire_indices[ i*6+5 ] = i*2 + 2;
3346 }
3347 }
3348
3349 glGenVertexArrays( 1, &mw->vao );
3350 glGenBuffers( 1, &mw->vbo );
3351 glGenBuffers( 1, &mw->ebo );
3352 glBindVertexArray( mw->vao );
3353
3354 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
3355
3356 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
3357 glBindVertexArray( mw->vao );
3358
3359 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
3360 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
3361
3362 // XY
3363 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
3364 glEnableVertexAttribArray( 0 );
3365
3366 VG_CHECK_GL();
3367
3368 mw->em = vg_list_size( wire_indices );
3369 }
3370
3371 // Create info data texture
3372 {
3373 glGenTextures( 1, &world.background_data );
3374 glBindTexture( GL_TEXTURE_2D, world.background_data );
3375 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
3376 vg_tex2d_nearest();
3377 }
3378
3379 // Create random smaples texture
3380 {
3381 u8 *data = malloc(512*512*2);
3382 for( int i = 0; i < 512*512*2; i ++ )
3383 data[ i ] = rand()/(RAND_MAX/255);
3384
3385 glGenTextures( 1, &world.random_samples );
3386 glBindTexture( GL_TEXTURE_2D, world.random_samples );
3387 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
3388 vg_tex2d_linear();
3389 vg_tex2d_repeat();
3390
3391 free( data );
3392 }
3393
3394 resource_load_main();
3395
3396 // Restore gamestate
3397 career_local_data_init();
3398 career_load();
3399 }
3400
3401 void vg_free(void)
3402 {
3403 sw_free_opengl();
3404 console_save_map( 0, NULL );
3405 career_serialize();
3406
3407 resource_free_main();
3408
3409 glDeleteTextures( 1, &world.background_data );
3410 glDeleteTextures( 1, &world.random_samples );
3411
3412 glDeleteVertexArrays( 1, &world.wire.vao );
3413 glDeleteBuffers( 1, &world.wire.vbo );
3414 glDeleteBuffers( 1, &world.wire.ebo );
3415
3416 free_mesh( &world.shapes );
3417
3418 map_free();
3419 }
3420
3421 int main( int argc, char *argv[] )
3422 {
3423 vg_init( argc, argv, "Marble Computing" );
3424 return 0;
3425 }