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